@mpxjs/webpack-plugin 2.10.15-4 → 2.10.15
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/LICENSE +433 -0
- package/lib/index.js +2 -5
- package/lib/platform/template/wx/component-config/progress.js +12 -0
- package/lib/platform/template/wx/component-config/unsupported.js +1 -1
- package/lib/platform/template/wx/index.js +3 -1
- package/lib/runtime/components/react/dist/getInnerListeners.js +35 -21
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +102 -34
- package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +3 -5
- package/lib/runtime/components/react/dist/mpx-progress.jsx +159 -0
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +51 -9
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +9 -16
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +20 -1
- package/lib/runtime/components/react/getInnerListeners.ts +41 -22
- package/lib/runtime/components/react/mpx-movable-view.tsx +156 -48
- package/lib/runtime/components/react/mpx-portal/portal-manager.tsx +4 -8
- package/lib/runtime/components/react/mpx-progress.tsx +257 -0
- package/lib/runtime/components/react/mpx-scroll-view.tsx +54 -9
- package/lib/runtime/components/react/mpx-swiper.tsx +9 -16
- package/lib/runtime/components/react/mpx-web-view.tsx +22 -1
- package/lib/runtime/components/react/types/getInnerListeners.d.ts +7 -2
- package/lib/runtime/components/web/mpx-movable-area.vue +43 -19
- package/lib/runtime/components/web/mpx-movable-view.vue +93 -3
- package/lib/runtime/components/web/mpx-swiper.vue +1 -2
- package/lib/runtime/components/web/mpx-web-view.vue +3 -3
- package/lib/template-compiler/compiler.js +61 -31
- package/lib/wxss/utils.js +1 -1
- package/package.json +3 -3
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* ✔ out-of-bounds
|
|
5
5
|
* ✔ x
|
|
6
6
|
* ✔ y
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* ✔ damping
|
|
8
|
+
* ✔ friction
|
|
9
9
|
* ✔ disabled
|
|
10
10
|
* ✘ scale
|
|
11
11
|
* ✘ scale-min
|
|
@@ -24,8 +24,96 @@ import useNodesRef from './useNodesRef';
|
|
|
24
24
|
import { MovableAreaContext } from './context';
|
|
25
25
|
import { useTransformStyle, splitProps, splitStyle, HIDDEN_STYLE, wrapChildren, flatGesture, extendObject, omit, useNavigation, useRunOnJSCallback } from './utils';
|
|
26
26
|
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
|
|
27
|
-
import Animated, { useSharedValue, useAnimatedStyle,
|
|
27
|
+
import Animated, { useSharedValue, useAnimatedStyle, runOnJS, runOnUI, withTiming, Easing } from 'react-native-reanimated';
|
|
28
28
|
import { collectDataset, noop } from '@mpxjs/utils';
|
|
29
|
+
// 超出边界处理函数,参考微信小程序的超出边界衰减效果
|
|
30
|
+
const applyBoundaryDecline = (newValue, range) => {
|
|
31
|
+
'worklet';
|
|
32
|
+
const decline = (distance) => {
|
|
33
|
+
'worklet';
|
|
34
|
+
return Math.sqrt(Math.abs(distance));
|
|
35
|
+
};
|
|
36
|
+
if (newValue < range[0]) {
|
|
37
|
+
const overDistance = range[0] - newValue;
|
|
38
|
+
return range[0] - decline(overDistance);
|
|
39
|
+
}
|
|
40
|
+
else if (newValue > range[1]) {
|
|
41
|
+
const overDistance = newValue - range[1];
|
|
42
|
+
return range[1] + decline(overDistance);
|
|
43
|
+
}
|
|
44
|
+
return newValue;
|
|
45
|
+
};
|
|
46
|
+
// 参考微信小程序的弹簧阻尼系统实现
|
|
47
|
+
const withWechatSpring = (toValue, dampingParam = 20, callback) => {
|
|
48
|
+
'worklet';
|
|
49
|
+
// 弹簧参数计算
|
|
50
|
+
const m = 1; // 质量
|
|
51
|
+
const k = 9 * Math.pow(dampingParam, 2) / 40; // 弹簧系数
|
|
52
|
+
const c = dampingParam; // 阻尼系数
|
|
53
|
+
// 判别式:r = c² - 4mk
|
|
54
|
+
const discriminant = c * c - 4 * m * k;
|
|
55
|
+
// 计算动画持续时间和缓动函数
|
|
56
|
+
let duration;
|
|
57
|
+
let easingFunction;
|
|
58
|
+
if (Math.abs(discriminant) < 0.01) {
|
|
59
|
+
// 临界阻尼 (discriminant ≈ 0)
|
|
60
|
+
// 使用cubic-out模拟临界阻尼的平滑过渡
|
|
61
|
+
duration = Math.max(350, Math.min(800, 2000 / dampingParam));
|
|
62
|
+
easingFunction = Easing.out(Easing.cubic);
|
|
63
|
+
}
|
|
64
|
+
else if (discriminant > 0) {
|
|
65
|
+
// 过阻尼 (discriminant > 0)
|
|
66
|
+
// 使用指数缓动模拟过阻尼的缓慢收敛
|
|
67
|
+
duration = Math.max(450, Math.min(1000, 2500 / dampingParam));
|
|
68
|
+
easingFunction = Easing.out(Easing.exp);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// 欠阻尼 (discriminant < 0) - 会产生振荡
|
|
72
|
+
// 计算振荡频率和衰减率
|
|
73
|
+
const dampingRatio = c / (2 * Math.sqrt(m * k)); // 阻尼比
|
|
74
|
+
// 根据阻尼比调整动画参数
|
|
75
|
+
if (dampingRatio < 0.7) {
|
|
76
|
+
// 明显振荡
|
|
77
|
+
duration = Math.max(600, Math.min(1200, 3000 / dampingParam));
|
|
78
|
+
// 创建带振荡的贝塞尔曲线
|
|
79
|
+
easingFunction = Easing.bezier(0.175, 0.885, 0.32, 1.275);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// 轻微振荡
|
|
83
|
+
duration = Math.max(400, Math.min(800, 2000 / dampingParam));
|
|
84
|
+
easingFunction = Easing.bezier(0.25, 0.46, 0.45, 0.94);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return withTiming(toValue, {
|
|
88
|
+
duration,
|
|
89
|
+
easing: easingFunction
|
|
90
|
+
}, callback);
|
|
91
|
+
};
|
|
92
|
+
// 参考微信小程序friction的惯性动画
|
|
93
|
+
const withWechatDecay = (velocity, currentPosition, clampRange, frictionValue = 2, callback) => {
|
|
94
|
+
'worklet';
|
|
95
|
+
// 微信小程序friction算法: delta = -1.5 * v² / a, 其中 a = -f * v / |v|
|
|
96
|
+
// 如果friction小于等于0,设置为默认值2
|
|
97
|
+
const validFriction = frictionValue <= 0 ? 2 : frictionValue;
|
|
98
|
+
const f = 1000 * validFriction;
|
|
99
|
+
const acceleration = velocity !== 0 ? -f * velocity / Math.abs(velocity) : 0;
|
|
100
|
+
const delta = acceleration !== 0 ? (-1.5 * velocity * velocity) / acceleration : 0;
|
|
101
|
+
let finalPosition = currentPosition + delta;
|
|
102
|
+
// 边界限制
|
|
103
|
+
if (finalPosition < clampRange[0]) {
|
|
104
|
+
finalPosition = clampRange[0];
|
|
105
|
+
}
|
|
106
|
+
else if (finalPosition > clampRange[1]) {
|
|
107
|
+
finalPosition = clampRange[1];
|
|
108
|
+
}
|
|
109
|
+
// 计算动画时长
|
|
110
|
+
const distance = Math.abs(finalPosition - currentPosition);
|
|
111
|
+
const duration = Math.min(1500, Math.max(200, distance * 8));
|
|
112
|
+
return withTiming(finalPosition, {
|
|
113
|
+
duration,
|
|
114
|
+
easing: Easing.out(Easing.cubic)
|
|
115
|
+
}, callback);
|
|
116
|
+
};
|
|
29
117
|
const styles = StyleSheet.create({
|
|
30
118
|
container: {
|
|
31
119
|
position: 'absolute',
|
|
@@ -41,7 +129,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
41
129
|
const hasLayoutRef = useRef(false);
|
|
42
130
|
const propsRef = useRef({});
|
|
43
131
|
propsRef.current = (props || {});
|
|
44
|
-
const { x = 0, y = 0, inertia = false, disabled = false, animation = true, 'out-of-bounds': outOfBounds = false, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, direction = 'none', 'disable-event-passthrough': disableEventPassthrough = false, 'simultaneous-handlers': originSimultaneousHandlers = [], 'wait-for': waitFor = [], style = {}, changeThrottleTime = 60, bindtouchstart, catchtouchstart, bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove, bindtouchend, catchtouchend, bindchange } = props;
|
|
132
|
+
const { x = 0, y = 0, inertia = false, disabled = false, animation = true, damping = 20, friction = 2, 'out-of-bounds': outOfBounds = false, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, direction = 'none', 'disable-event-passthrough': disableEventPassthrough = false, 'simultaneous-handlers': originSimultaneousHandlers = [], 'wait-for': waitFor = [], style = {}, changeThrottleTime = 60, bindtouchstart, catchtouchstart, bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove, bindtouchend, catchtouchend, bindchange } = props;
|
|
45
133
|
const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(Object.assign({}, style, styles.container), { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
46
134
|
const navigation = useNavigation();
|
|
47
135
|
const prevSimultaneousHandlersRef = useRef(originSimultaneousHandlers || []);
|
|
@@ -106,18 +194,12 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
106
194
|
const { x: newX, y: newY } = checkBoundaryPosition({ positionX: Number(x), positionY: Number(y) });
|
|
107
195
|
if (direction === 'horizontal' || direction === 'all') {
|
|
108
196
|
offsetX.value = animation
|
|
109
|
-
?
|
|
110
|
-
duration: 1500,
|
|
111
|
-
dampingRatio: 0.8
|
|
112
|
-
})
|
|
197
|
+
? withWechatSpring(newX, damping)
|
|
113
198
|
: newX;
|
|
114
199
|
}
|
|
115
200
|
if (direction === 'vertical' || direction === 'all') {
|
|
116
201
|
offsetY.value = animation
|
|
117
|
-
?
|
|
118
|
-
duration: 1500,
|
|
119
|
-
dampingRatio: 0.8
|
|
120
|
-
})
|
|
202
|
+
? withWechatSpring(newY, damping)
|
|
121
203
|
: newY;
|
|
122
204
|
}
|
|
123
205
|
if (bindchange) {
|
|
@@ -293,7 +375,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
293
375
|
triggerEndOnJS
|
|
294
376
|
});
|
|
295
377
|
const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef);
|
|
296
|
-
// 节流版本的
|
|
378
|
+
// 节流版本的change事件触发
|
|
297
379
|
const handleTriggerChangeThrottled = useCallback(({ x, y, type }) => {
|
|
298
380
|
'worklet';
|
|
299
381
|
const now = Date.now();
|
|
@@ -357,7 +439,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
357
439
|
offsetX.value = x;
|
|
358
440
|
}
|
|
359
441
|
else {
|
|
360
|
-
offsetX.value = newX;
|
|
442
|
+
offsetX.value = applyBoundaryDecline(newX, draggableXRange.value);
|
|
361
443
|
}
|
|
362
444
|
}
|
|
363
445
|
if (direction === 'vertical' || direction === 'all') {
|
|
@@ -367,7 +449,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
367
449
|
offsetY.value = y;
|
|
368
450
|
}
|
|
369
451
|
else {
|
|
370
|
-
offsetY.value = newY;
|
|
452
|
+
offsetY.value = applyBoundaryDecline(newY, draggableYRange.value);
|
|
371
453
|
}
|
|
372
454
|
}
|
|
373
455
|
if (bindchange) {
|
|
@@ -397,18 +479,12 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
397
479
|
if (x !== offsetX.value || y !== offsetY.value) {
|
|
398
480
|
if (x !== offsetX.value) {
|
|
399
481
|
offsetX.value = animation
|
|
400
|
-
?
|
|
401
|
-
duration: 1500,
|
|
402
|
-
dampingRatio: 0.8
|
|
403
|
-
})
|
|
482
|
+
? withWechatSpring(x, damping)
|
|
404
483
|
: x;
|
|
405
484
|
}
|
|
406
485
|
if (y !== offsetY.value) {
|
|
407
486
|
offsetY.value = animation
|
|
408
|
-
?
|
|
409
|
-
duration: 1500,
|
|
410
|
-
dampingRatio: 0.8
|
|
411
|
-
})
|
|
487
|
+
? withWechatSpring(y, damping)
|
|
412
488
|
: y;
|
|
413
489
|
}
|
|
414
490
|
if (bindchange) {
|
|
@@ -420,14 +496,10 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
420
496
|
}
|
|
421
497
|
}
|
|
422
498
|
else if (inertia) {
|
|
423
|
-
// 惯性处理
|
|
499
|
+
// 惯性处理 - 使用微信小程序friction算法
|
|
424
500
|
if (direction === 'horizontal' || direction === 'all') {
|
|
425
501
|
xInertialMotion.value = true;
|
|
426
|
-
offsetX.value =
|
|
427
|
-
velocity: e.velocityX / 10,
|
|
428
|
-
rubberBandEffect: outOfBounds,
|
|
429
|
-
clamp: draggableXRange.value
|
|
430
|
-
}, () => {
|
|
502
|
+
offsetX.value = withWechatDecay(e.velocityX / 10, offsetX.value, draggableXRange.value, friction, () => {
|
|
431
503
|
xInertialMotion.value = false;
|
|
432
504
|
if (bindchange) {
|
|
433
505
|
runOnJS(runOnJSCallback)('handleTriggerChange', {
|
|
@@ -439,11 +511,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
439
511
|
}
|
|
440
512
|
if (direction === 'vertical' || direction === 'all') {
|
|
441
513
|
yInertialMotion.value = true;
|
|
442
|
-
offsetY.value =
|
|
443
|
-
velocity: e.velocityY / 10,
|
|
444
|
-
rubberBandEffect: outOfBounds,
|
|
445
|
-
clamp: draggableYRange.value
|
|
446
|
-
}, () => {
|
|
514
|
+
offsetY.value = withWechatDecay(e.velocityY / 10, offsetY.value, draggableYRange.value, friction, () => {
|
|
447
515
|
yInertialMotion.value = false;
|
|
448
516
|
if (bindchange) {
|
|
449
517
|
runOnJS(runOnJSCallback)('handleTriggerChange', {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback, forwardRef, useImperativeHandle } from 'react';
|
|
2
|
-
import { View, StyleSheet } from 'react-native';
|
|
1
|
+
import { useState, useCallback, forwardRef, useImperativeHandle, Fragment } from 'react';
|
|
3
2
|
const _PortalManager = forwardRef((props, ref) => {
|
|
4
3
|
const [state, setState] = useState({
|
|
5
4
|
portals: []
|
|
@@ -31,10 +30,9 @@ const _PortalManager = forwardRef((props, ref) => {
|
|
|
31
30
|
portals: state.portals
|
|
32
31
|
}));
|
|
33
32
|
return (<>
|
|
34
|
-
{state.portals.map(({ key, children }
|
|
35
|
-
style={[StyleSheet.absoluteFill, { zIndex: 1000 + i, pointerEvents: 'box-none' }]}>
|
|
33
|
+
{state.portals.map(({ key, children }) => (<Fragment key={key}>
|
|
36
34
|
{children}
|
|
37
|
-
</
|
|
35
|
+
</Fragment>))}
|
|
38
36
|
</>);
|
|
39
37
|
});
|
|
40
38
|
export default _PortalManager;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ✔ percent 进度百分比 0-100
|
|
3
|
+
* ✘ show-info 在进度条右侧显示百分比
|
|
4
|
+
* ✘ border-radius 圆角大小
|
|
5
|
+
* ✘ font-size 右侧百分比字体大小
|
|
6
|
+
* ✔ stroke-width 进度条线的宽度
|
|
7
|
+
* ✔ color 进度条颜色(请使用activeColor)
|
|
8
|
+
* ✔ activeColor 已选择的进度条的颜色
|
|
9
|
+
* ✔ backgroundColor 未选择的进度条的颜色
|
|
10
|
+
* ✔ active 进度条从左往右的动画
|
|
11
|
+
* ✔ active-mode backwards: 动画从头播;forwards:动画从上次结束点接着播
|
|
12
|
+
* ✔ duration 进度增加1%所需毫秒数
|
|
13
|
+
* ✔ bindactiveend 动画完成事件
|
|
14
|
+
*/
|
|
15
|
+
import { useRef, forwardRef, useEffect, useState, createElement } from 'react';
|
|
16
|
+
import { View } from 'react-native';
|
|
17
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing, runOnJS } from 'react-native-reanimated';
|
|
18
|
+
import useInnerProps from './getInnerListeners';
|
|
19
|
+
import useNodesRef from './useNodesRef';
|
|
20
|
+
import { useLayout, useTransformStyle, extendObject } from './utils';
|
|
21
|
+
import Portal from './mpx-portal';
|
|
22
|
+
const Progress = forwardRef((props, ref) => {
|
|
23
|
+
const { percent = 0, 'stroke-width': strokeWidth = 6, color, activeColor = color || '#09BB07', backgroundColor = '#EBEBEB', active = false, 'active-mode': activeMode = 'backwards', duration = 30, bindactiveend, style = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
24
|
+
const nodeRef = useRef(null);
|
|
25
|
+
const propsRef = useRef({});
|
|
26
|
+
propsRef.current = props;
|
|
27
|
+
// 进度值状态
|
|
28
|
+
const [lastPercent, setLastPercent] = useState(0);
|
|
29
|
+
const progressWidth = useSharedValue(0);
|
|
30
|
+
const { normalStyle, hasSelfPercent, setWidth, setHeight, hasPositionFixed } = useTransformStyle(style, {
|
|
31
|
+
enableVar,
|
|
32
|
+
externalVarContext,
|
|
33
|
+
parentFontSize,
|
|
34
|
+
parentWidth,
|
|
35
|
+
parentHeight
|
|
36
|
+
});
|
|
37
|
+
const { layoutRef, layoutStyle, layoutProps } = useLayout({
|
|
38
|
+
props,
|
|
39
|
+
hasSelfPercent,
|
|
40
|
+
setWidth,
|
|
41
|
+
setHeight,
|
|
42
|
+
nodeRef
|
|
43
|
+
});
|
|
44
|
+
useNodesRef(props, ref, nodeRef, {
|
|
45
|
+
style: normalStyle
|
|
46
|
+
});
|
|
47
|
+
// 进度条动画函数
|
|
48
|
+
const startProgressAnimation = (targetPercent, startPercent, animationDuration, onFinished) => {
|
|
49
|
+
// 根据 active-mode 设置起始位置
|
|
50
|
+
progressWidth.value = startPercent;
|
|
51
|
+
progressWidth.value = withTiming(targetPercent, {
|
|
52
|
+
duration: animationDuration,
|
|
53
|
+
easing: Easing.linear
|
|
54
|
+
}, (finished) => {
|
|
55
|
+
if (finished && onFinished) {
|
|
56
|
+
// 在动画回调中,需要使用runOnJS回到主线程
|
|
57
|
+
runOnJS(onFinished)();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
// 创建在主线程执行的事件回调函数
|
|
62
|
+
const triggerActiveEnd = (percent) => {
|
|
63
|
+
if (bindactiveend) {
|
|
64
|
+
bindactiveend({
|
|
65
|
+
type: 'activeend',
|
|
66
|
+
detail: {
|
|
67
|
+
percent: percent
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
// 进度变化时的动画效果
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const targetPercent = Math.max(0, Math.min(100, percent));
|
|
75
|
+
if (active) {
|
|
76
|
+
// 根据 active-mode 确定起始位置
|
|
77
|
+
let startPercent;
|
|
78
|
+
if (activeMode === 'backwards') {
|
|
79
|
+
startPercent = 0;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// forwards 模式:使用上次记录的百分比作为起始位置
|
|
83
|
+
startPercent = lastPercent;
|
|
84
|
+
}
|
|
85
|
+
// 计算动画持续时间
|
|
86
|
+
const percentDiff = Math.abs(targetPercent - startPercent);
|
|
87
|
+
const animationDuration = percentDiff * duration;
|
|
88
|
+
// 创建动画完成回调
|
|
89
|
+
const onAnimationFinished = () => {
|
|
90
|
+
triggerActiveEnd(targetPercent);
|
|
91
|
+
};
|
|
92
|
+
// 执行动画
|
|
93
|
+
startProgressAnimation(targetPercent, startPercent, animationDuration, onAnimationFinished);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
progressWidth.value = targetPercent;
|
|
97
|
+
}
|
|
98
|
+
setLastPercent(targetPercent);
|
|
99
|
+
}, [percent, active, activeMode, duration, bindactiveend]);
|
|
100
|
+
// 初始化时设置进度值
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (!active) {
|
|
103
|
+
progressWidth.value = Math.max(0, Math.min(100, percent));
|
|
104
|
+
}
|
|
105
|
+
}, []);
|
|
106
|
+
// 进度条动画样式
|
|
107
|
+
const animatedProgressStyle = useAnimatedStyle(() => {
|
|
108
|
+
return {
|
|
109
|
+
width: `${progressWidth.value}%`
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
// 确保数值类型正确
|
|
113
|
+
const strokeWidthNum = typeof strokeWidth === 'number' ? strokeWidth : parseInt(strokeWidth, 10) || 6;
|
|
114
|
+
// 容器样式
|
|
115
|
+
const containerStyle = extendObject({}, {
|
|
116
|
+
flexDirection: 'row',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
width: '100%',
|
|
119
|
+
minHeight: Math.max(strokeWidthNum, 20)
|
|
120
|
+
}, normalStyle, layoutStyle);
|
|
121
|
+
// 进度条背景样式
|
|
122
|
+
const progressBgStyle = {
|
|
123
|
+
width: '100%',
|
|
124
|
+
height: strokeWidthNum,
|
|
125
|
+
backgroundColor,
|
|
126
|
+
overflow: 'hidden'
|
|
127
|
+
};
|
|
128
|
+
// 进度条填充样式
|
|
129
|
+
const progressFillStyle = {
|
|
130
|
+
height: '100%',
|
|
131
|
+
backgroundColor: activeColor
|
|
132
|
+
};
|
|
133
|
+
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
134
|
+
ref: nodeRef
|
|
135
|
+
}), [
|
|
136
|
+
'percent',
|
|
137
|
+
'stroke-width',
|
|
138
|
+
'color',
|
|
139
|
+
'activeColor',
|
|
140
|
+
'backgroundColor',
|
|
141
|
+
'active',
|
|
142
|
+
'active-mode',
|
|
143
|
+
'duration',
|
|
144
|
+
'bindactiveend'
|
|
145
|
+
], { layoutRef });
|
|
146
|
+
const progressComponent = createElement(View, extendObject({}, innerProps, { style: containerStyle }),
|
|
147
|
+
// 进度条背景
|
|
148
|
+
createElement(View, { style: progressBgStyle },
|
|
149
|
+
// 进度条填充
|
|
150
|
+
createElement(Animated.View, {
|
|
151
|
+
style: [progressFillStyle, animatedProgressStyle]
|
|
152
|
+
})));
|
|
153
|
+
if (hasPositionFixed) {
|
|
154
|
+
return createElement(Portal, null, progressComponent);
|
|
155
|
+
}
|
|
156
|
+
return progressComponent;
|
|
157
|
+
});
|
|
158
|
+
Progress.displayName = 'MpxProgress';
|
|
159
|
+
export default Progress;
|
|
@@ -74,11 +74,22 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
74
74
|
black: ['#000'],
|
|
75
75
|
white: ['#fff']
|
|
76
76
|
};
|
|
77
|
+
const isContentSizeChange = useRef(false);
|
|
77
78
|
const { refresherContent, otherContent } = getRefresherContent(props.children);
|
|
78
79
|
const hasRefresher = refresherContent && refresherEnabled;
|
|
79
80
|
const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, hasPositionFixed, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
80
81
|
const { textStyle, innerStyle = {} } = splitStyle(normalStyle);
|
|
81
82
|
const scrollViewRef = useRef(null);
|
|
83
|
+
const propsRef = useRef(props);
|
|
84
|
+
const refresherStateRef = useRef({
|
|
85
|
+
hasRefresher,
|
|
86
|
+
refresherTriggered
|
|
87
|
+
});
|
|
88
|
+
propsRef.current = props;
|
|
89
|
+
refresherStateRef.current = {
|
|
90
|
+
hasRefresher,
|
|
91
|
+
refresherTriggered
|
|
92
|
+
};
|
|
82
93
|
const runOnJSCallbackRef = useRef({
|
|
83
94
|
setEnableScroll,
|
|
84
95
|
setScrollBounces,
|
|
@@ -213,7 +224,22 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
213
224
|
}
|
|
214
225
|
}
|
|
215
226
|
function onContentSizeChange(width, height) {
|
|
216
|
-
|
|
227
|
+
isContentSizeChange.current = true;
|
|
228
|
+
const newContentLength = selectLength({ height, width });
|
|
229
|
+
const oldContentLength = scrollOptions.current.contentLength;
|
|
230
|
+
scrollOptions.current.contentLength = newContentLength;
|
|
231
|
+
// 内容高度变化时,Animated.event 的映射可能会有不生效的场景,所以需要手动设置一下 scrollOffset 的值
|
|
232
|
+
if (enableSticky && (__mpx_mode__ === 'android' || __mpx_mode__ === 'ios')) {
|
|
233
|
+
// 当内容变少时,检查当前滚动位置是否超出新的内容范围
|
|
234
|
+
if (newContentLength < oldContentLength) {
|
|
235
|
+
const { visibleLength, offset } = scrollOptions.current;
|
|
236
|
+
const maxOffset = Math.max(0, newContentLength - visibleLength);
|
|
237
|
+
// 如果当前滚动位置超出了新的内容范围,调整滚动offset
|
|
238
|
+
if (offset > maxOffset && scrollY) {
|
|
239
|
+
scrollOffset.setValue(maxOffset);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
217
243
|
}
|
|
218
244
|
function onLayout(e) {
|
|
219
245
|
const layout = e.nativeEvent.layout || {};
|
|
@@ -233,8 +259,9 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
233
259
|
}
|
|
234
260
|
function onScroll(e) {
|
|
235
261
|
const { bindscroll } = props;
|
|
236
|
-
const {
|
|
237
|
-
const {
|
|
262
|
+
const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
|
|
263
|
+
const { x: scrollLeft, y: scrollTop } = contentOffset;
|
|
264
|
+
const { width: scrollWidth, height: scrollHeight } = contentSize;
|
|
238
265
|
isAtTop.value = scrollTop <= 0;
|
|
239
266
|
bindscroll &&
|
|
240
267
|
bindscroll(getCustomEvent('scroll', e, {
|
|
@@ -244,7 +271,8 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
244
271
|
scrollHeight,
|
|
245
272
|
scrollWidth,
|
|
246
273
|
deltaX: scrollLeft - scrollOptions.current.scrollLeft,
|
|
247
|
-
deltaY: scrollTop - scrollOptions.current.scrollTop
|
|
274
|
+
deltaY: scrollTop - scrollOptions.current.scrollTop,
|
|
275
|
+
layoutMeasurement
|
|
248
276
|
},
|
|
249
277
|
layoutRef
|
|
250
278
|
}, props));
|
|
@@ -257,8 +285,9 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
257
285
|
}
|
|
258
286
|
function onScrollEnd(e) {
|
|
259
287
|
const { bindscrollend } = props;
|
|
260
|
-
const {
|
|
261
|
-
const {
|
|
288
|
+
const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
|
|
289
|
+
const { x: scrollLeft, y: scrollTop } = contentOffset;
|
|
290
|
+
const { width: scrollWidth, height: scrollHeight } = contentSize;
|
|
262
291
|
isAtTop.value = scrollTop <= 0;
|
|
263
292
|
bindscrollend &&
|
|
264
293
|
bindscrollend(getCustomEvent('scrollend', e, {
|
|
@@ -266,7 +295,8 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
266
295
|
scrollLeft,
|
|
267
296
|
scrollTop,
|
|
268
297
|
scrollHeight,
|
|
269
|
-
scrollWidth
|
|
298
|
+
scrollWidth,
|
|
299
|
+
layoutMeasurement
|
|
270
300
|
},
|
|
271
301
|
layoutRef
|
|
272
302
|
}, props));
|
|
@@ -313,6 +343,16 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
313
343
|
const scrollHandler = RNAnimated.event([{ nativeEvent: { contentOffset: { y: scrollOffset } } }], {
|
|
314
344
|
useNativeDriver: true,
|
|
315
345
|
listener: (event) => {
|
|
346
|
+
const y = event.nativeEvent.contentOffset.y || 0;
|
|
347
|
+
// 内容高度变化时,鸿蒙中 listener 回调通过scrollOffset.__getValue获取值一直等于event.nativeEvent.contentOffset.y,值是正确的,但是无法触发 sticky 动画执行,所以需要手动再 set 一次
|
|
348
|
+
if (__mpx_mode__ === 'harmony') {
|
|
349
|
+
if (isContentSizeChange.current) {
|
|
350
|
+
scrollOffset.setValue(y);
|
|
351
|
+
setTimeout(() => {
|
|
352
|
+
isContentSizeChange.current = false;
|
|
353
|
+
}, 100);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
316
356
|
onScroll(event);
|
|
317
357
|
}
|
|
318
358
|
});
|
|
@@ -347,6 +387,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
347
387
|
}
|
|
348
388
|
// 处理刷新
|
|
349
389
|
function onRefresh() {
|
|
390
|
+
const { hasRefresher, refresherTriggered } = refresherStateRef.current;
|
|
350
391
|
if (hasRefresher && refresherTriggered === undefined) {
|
|
351
392
|
// 处理使用了自定义刷新组件,又没设置 refresherTriggered 的情况
|
|
352
393
|
setRefreshing(true);
|
|
@@ -358,9 +399,9 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
358
399
|
}
|
|
359
400
|
}, 500);
|
|
360
401
|
}
|
|
361
|
-
const { bindrefresherrefresh } =
|
|
402
|
+
const { bindrefresherrefresh } = propsRef.current;
|
|
362
403
|
bindrefresherrefresh &&
|
|
363
|
-
bindrefresherrefresh(getCustomEvent('refresherrefresh', {}, { layoutRef },
|
|
404
|
+
bindrefresherrefresh(getCustomEvent('refresherrefresh', {}, { layoutRef }, propsRef.current));
|
|
364
405
|
}
|
|
365
406
|
function getRefresherContent(children) {
|
|
366
407
|
let refresherContent = null;
|
|
@@ -499,6 +540,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
499
540
|
showsVerticalScrollIndicator: scrollY && showScrollbar,
|
|
500
541
|
scrollEnabled: !enableScroll ? false : !!(scrollX || scrollY),
|
|
501
542
|
bounces: false,
|
|
543
|
+
overScrollMode: 'never',
|
|
502
544
|
ref: scrollViewRef,
|
|
503
545
|
onScroll: enableSticky ? scrollHandler : onScroll,
|
|
504
546
|
onContentSizeChange: onContentSizeChange,
|
|
@@ -120,8 +120,6 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
120
120
|
const strVelocity = 'velocity' + dir.toUpperCase();
|
|
121
121
|
// 标识手指触摸和抬起, 起点在onBegin
|
|
122
122
|
const touchfinish = useSharedValue(true);
|
|
123
|
-
// 记录onUpdate时的方向,用于进行onFinalize中的值修正
|
|
124
|
-
const preUpdateTransDir = useSharedValue(0);
|
|
125
123
|
// 记录上一帧的绝对定位坐标
|
|
126
124
|
const preAbsolutePos = useSharedValue(0);
|
|
127
125
|
// 记录从onBegin 到 onTouchesUp 时移动的距离
|
|
@@ -327,9 +325,11 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
327
325
|
resumeLoop
|
|
328
326
|
};
|
|
329
327
|
}, []);
|
|
330
|
-
function handleSwiperChange(current) {
|
|
331
|
-
|
|
332
|
-
|
|
328
|
+
function handleSwiperChange(current, pCurrent) {
|
|
329
|
+
if (pCurrent !== currentIndex.value) {
|
|
330
|
+
const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef });
|
|
331
|
+
bindchange && bindchange(eventData);
|
|
332
|
+
}
|
|
333
333
|
}
|
|
334
334
|
const runOnJSCallbackRef = useRef({
|
|
335
335
|
loop,
|
|
@@ -379,7 +379,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
379
379
|
// 1. 用户在当前页切换选中项,动画;用户携带选中index打开到swiper页直接选中不走动画
|
|
380
380
|
useAnimatedReaction(() => currentIndex.value, (newIndex, preIndex) => {
|
|
381
381
|
// 这里必须传递函数名, 直接写()=> {}形式会报 访问了未sharedValue信息
|
|
382
|
-
if (newIndex !== preIndex &&
|
|
382
|
+
if (newIndex !== preIndex && bindchange) {
|
|
383
383
|
runOnJS(runOnJSCallback)('handleSwiperChange', newIndex, propCurrent);
|
|
384
384
|
}
|
|
385
385
|
});
|
|
@@ -411,9 +411,9 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
411
411
|
}
|
|
412
412
|
}, [children.length]);
|
|
413
413
|
useEffect(() => {
|
|
414
|
-
// 1. 如果用户在touch的过程中, 外部更新了current
|
|
414
|
+
// 1. 如果用户在touch的过程中, 外部更新了current以外部为准(小程序表现)
|
|
415
415
|
// 2. 手指滑动过程中更新索引,外部会把current再传入进来,导致offset直接更新,增加判断不同才更新
|
|
416
|
-
if (propCurrent !== currentIndex.value
|
|
416
|
+
if (propCurrent !== currentIndex.value) {
|
|
417
417
|
updateCurrent(propCurrent, step.value);
|
|
418
418
|
}
|
|
419
419
|
}, [propCurrent]);
|
|
@@ -662,7 +662,6 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
662
662
|
translation: moveDistance,
|
|
663
663
|
transdir: moveDistance
|
|
664
664
|
};
|
|
665
|
-
preUpdateTransDir.value = moveDistance;
|
|
666
665
|
// 1. 支持滑动中超出一半更新索引的能力:只更新索引并不会影响onFinalize依据当前offset计算的索引
|
|
667
666
|
const { half } = computeHalf(eventData);
|
|
668
667
|
if (childrenLength.value > 1 && half) {
|
|
@@ -703,17 +702,11 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
703
702
|
if (touchfinish.value)
|
|
704
703
|
return;
|
|
705
704
|
touchfinish.value = true;
|
|
706
|
-
/**
|
|
707
|
-
* 安卓修正
|
|
708
|
-
* 问题:部分安卓机型onFinalize中拿到的absoluteX 有问题
|
|
709
|
-
* 案例:比如手指从右向左滑的时候,onUpdate拿到的是241.64346313476562, 而onFinalize中拿到的是241.81817626953125,理论上onFinalize中应该比onUpdate小才对吧
|
|
710
|
-
* 解决方式:修正
|
|
711
|
-
*/
|
|
712
705
|
// 触发过onUpdate正常情况下e[strAbso] - preAbsolutePos.value=0; 未触发过onUpdate的情况下e[strAbso] - preAbsolutePos.value 不为0
|
|
713
706
|
const moveDistance = e[strAbso] - preAbsolutePos.value;
|
|
714
707
|
const eventData = {
|
|
715
708
|
translation: moveDistance,
|
|
716
|
-
transdir:
|
|
709
|
+
transdir: moveDistance !== 0 ? moveDistance : e[strAbso] - moveTranstion.value
|
|
717
710
|
};
|
|
718
711
|
// 1. 只有一个元素:循环 和 非循环状态,都走回弹效果
|
|
719
712
|
if (childrenLength.value === 1) {
|
|
@@ -80,9 +80,25 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
80
80
|
useNodesRef(props, ref, webViewRef, {
|
|
81
81
|
style: defaultWebViewStyle
|
|
82
82
|
});
|
|
83
|
+
const hostValidate = (url) => {
|
|
84
|
+
const host = url && new URL(url).host;
|
|
85
|
+
const hostWhitelists = mpx.config.rnConfig?.webviewConfig?.hostWhitelists || [];
|
|
86
|
+
if (hostWhitelists.length) {
|
|
87
|
+
return hostWhitelists.some((item) => {
|
|
88
|
+
return host.endsWith(item);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
83
95
|
if (!src) {
|
|
84
96
|
return null;
|
|
85
97
|
}
|
|
98
|
+
if (!hostValidate(src)) {
|
|
99
|
+
console.error('访问页面域名不符合domainWhiteLists白名单配置,请确认是否正确配置该域名白名单');
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
86
102
|
const _reload = function () {
|
|
87
103
|
if (__mpx_mode__ !== 'ios') {
|
|
88
104
|
fristLoaded.current = false; // 安卓需要重新设置
|
|
@@ -133,6 +149,9 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
133
149
|
}
|
|
134
150
|
};
|
|
135
151
|
const _message = function (res) {
|
|
152
|
+
if (!hostValidate(res.nativeEvent?.url)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
136
155
|
let data = {};
|
|
137
156
|
let asyncCallback;
|
|
138
157
|
const navObj = promisify({ redirectTo, navigateTo, navigateBack, reLaunch, switchTab });
|
|
@@ -184,7 +203,7 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
184
203
|
break;
|
|
185
204
|
default:
|
|
186
205
|
if (type) {
|
|
187
|
-
const implement = mpx.config.webviewConfig.apiImplementations && mpx.config.webviewConfig.apiImplementations[type];
|
|
206
|
+
const implement = mpx.config.rnConfig.webviewConfig && mpx.config.rnConfig.webviewConfig.apiImplementations && mpx.config.rnConfig.webviewConfig.apiImplementations[type];
|
|
188
207
|
if (isFunction(implement)) {
|
|
189
208
|
asyncCallback = Promise.resolve(implement(...params));
|
|
190
209
|
}
|