@mpxjs/webpack-plugin 2.10.4-beta.18 → 2.10.4-beta.19-input
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/lib/runtime/components/react/dist/getInnerListeners.js +36 -22
- package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +145 -0
- package/lib/runtime/components/react/dist/mpx-button.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-canvas/Image.js +2 -4
- package/lib/runtime/components/react/dist/mpx-canvas/index.jsx +20 -17
- package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-checkbox.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-icon/index.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-image.jsx +33 -20
- package/lib/runtime/components/react/dist/mpx-input.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-label.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-movable-area.jsx +8 -3
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +205 -79
- package/lib/runtime/components/react/dist/mpx-picker/index.jsx +11 -13
- package/lib/runtime/components/react/dist/mpx-picker-view/index.jsx +8 -7
- package/lib/runtime/components/react/dist/mpx-picker-view-column/index.jsx +29 -11
- package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +3 -5
- package/lib/runtime/components/react/dist/mpx-progress.jsx +163 -0
- package/lib/runtime/components/react/dist/mpx-radio-group.jsx +9 -2
- package/lib/runtime/components/react/dist/mpx-radio.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +10 -2
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +104 -51
- package/lib/runtime/components/react/dist/mpx-slider.jsx +321 -0
- package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +3 -1
- package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +11 -9
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +203 -141
- package/lib/runtime/components/react/dist/mpx-switch.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-text.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-video.jsx +7 -2
- package/lib/runtime/components/react/dist/mpx-view.jsx +28 -26
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +34 -29
- package/lib/runtime/components/react/dist/useAnimationHooks.js +12 -89
- package/lib/runtime/components/react/dist/utils.jsx +199 -114
- package/lib/runtime/components/react/mpx-input.tsx +6 -6
- package/lib/template-compiler/compiler.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,163 @@
|
|
|
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, useRunOnJSCallback } 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, 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(props);
|
|
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
|
+
// 使用 useRunOnJSCallback 处理动画回调
|
|
48
|
+
const runOnJSCallbackRef = useRef({
|
|
49
|
+
triggerActiveEnd: (percent) => {
|
|
50
|
+
const currentProps = propsRef.current;
|
|
51
|
+
if (currentProps.bindactiveend) {
|
|
52
|
+
currentProps.bindactiveend({
|
|
53
|
+
type: 'activeend',
|
|
54
|
+
detail: {
|
|
55
|
+
percent: percent
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef);
|
|
62
|
+
// 进度条动画函数
|
|
63
|
+
const startProgressAnimation = (targetPercent, startPercent, animationDuration, onFinished) => {
|
|
64
|
+
// 根据 active-mode 设置起始位置
|
|
65
|
+
progressWidth.value = startPercent;
|
|
66
|
+
progressWidth.value = withTiming(targetPercent, {
|
|
67
|
+
duration: animationDuration,
|
|
68
|
+
easing: Easing.linear
|
|
69
|
+
}, (finished) => {
|
|
70
|
+
if (finished && onFinished) {
|
|
71
|
+
// 在动画回调中,执行传入的worklet函数
|
|
72
|
+
onFinished();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
// 进度变化时的动画效果
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const targetPercent = Math.max(0, Math.min(100, percent));
|
|
79
|
+
if (active) {
|
|
80
|
+
// 根据 active-mode 确定起始位置
|
|
81
|
+
let startPercent;
|
|
82
|
+
if (activeMode === 'backwards') {
|
|
83
|
+
startPercent = 0;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// forwards 模式:使用上次记录的百分比作为起始位置
|
|
87
|
+
startPercent = lastPercent;
|
|
88
|
+
}
|
|
89
|
+
// 计算动画持续时间
|
|
90
|
+
const percentDiff = Math.abs(targetPercent - startPercent);
|
|
91
|
+
const animationDuration = percentDiff * duration;
|
|
92
|
+
// 执行动画
|
|
93
|
+
startProgressAnimation(targetPercent, startPercent, animationDuration, () => {
|
|
94
|
+
'worklet';
|
|
95
|
+
// 在worklet函数内部执行runOnJS调用runOnJSCallback
|
|
96
|
+
runOnJS(runOnJSCallback)('triggerActiveEnd', targetPercent);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
progressWidth.value = targetPercent;
|
|
101
|
+
}
|
|
102
|
+
setLastPercent(targetPercent);
|
|
103
|
+
}, [percent, active, activeMode, duration]);
|
|
104
|
+
// 初始化时设置进度值
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!active) {
|
|
107
|
+
progressWidth.value = Math.max(0, Math.min(100, percent));
|
|
108
|
+
}
|
|
109
|
+
}, []);
|
|
110
|
+
// 进度条动画样式
|
|
111
|
+
const animatedProgressStyle = useAnimatedStyle(() => {
|
|
112
|
+
return {
|
|
113
|
+
width: `${progressWidth.value}%`
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
// 确保数值类型正确
|
|
117
|
+
const strokeWidthNum = typeof strokeWidth === 'number' ? strokeWidth : parseInt(strokeWidth, 10) || 6;
|
|
118
|
+
// 容器样式
|
|
119
|
+
const containerStyle = extendObject({}, {
|
|
120
|
+
flexDirection: 'row',
|
|
121
|
+
alignItems: 'center',
|
|
122
|
+
width: '100%',
|
|
123
|
+
minHeight: Math.max(strokeWidthNum, 20)
|
|
124
|
+
}, normalStyle, layoutStyle);
|
|
125
|
+
// 进度条背景样式
|
|
126
|
+
const progressBgStyle = {
|
|
127
|
+
width: '100%',
|
|
128
|
+
height: strokeWidthNum,
|
|
129
|
+
backgroundColor,
|
|
130
|
+
overflow: 'hidden'
|
|
131
|
+
};
|
|
132
|
+
// 进度条填充样式
|
|
133
|
+
const progressFillStyle = {
|
|
134
|
+
height: '100%',
|
|
135
|
+
backgroundColor: activeColor
|
|
136
|
+
};
|
|
137
|
+
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
138
|
+
ref: nodeRef
|
|
139
|
+
}), [
|
|
140
|
+
'percent',
|
|
141
|
+
'stroke-width',
|
|
142
|
+
'color',
|
|
143
|
+
'activeColor',
|
|
144
|
+
'backgroundColor',
|
|
145
|
+
'active',
|
|
146
|
+
'active-mode',
|
|
147
|
+
'duration',
|
|
148
|
+
'bindactiveend'
|
|
149
|
+
], { layoutRef });
|
|
150
|
+
const progressComponent = createElement(View, extendObject({}, innerProps, { style: containerStyle }),
|
|
151
|
+
// 进度条背景
|
|
152
|
+
createElement(View, { style: progressBgStyle },
|
|
153
|
+
// 进度条填充
|
|
154
|
+
createElement(Animated.View, {
|
|
155
|
+
style: [progressFillStyle, animatedProgressStyle]
|
|
156
|
+
})));
|
|
157
|
+
if (hasPositionFixed) {
|
|
158
|
+
return createElement(Portal, null, progressComponent);
|
|
159
|
+
}
|
|
160
|
+
return progressComponent;
|
|
161
|
+
});
|
|
162
|
+
Progress.displayName = 'MpxProgress';
|
|
163
|
+
export default Progress;
|
|
@@ -8,6 +8,7 @@ import { FormContext, RadioGroupContext } from './context';
|
|
|
8
8
|
import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
9
9
|
import useNodesRef from './useNodesRef';
|
|
10
10
|
import { useLayout, useTransformStyle, wrapChildren, extendObject } from './utils';
|
|
11
|
+
import Portal from './mpx-portal';
|
|
11
12
|
const radioGroup = forwardRef((props, ref) => {
|
|
12
13
|
const { style = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
13
14
|
const propsRef = useRef({});
|
|
@@ -23,7 +24,7 @@ const radioGroup = forwardRef((props, ref) => {
|
|
|
23
24
|
flexWrap: 'wrap'
|
|
24
25
|
};
|
|
25
26
|
const styleObj = extendObject({}, defaultStyle, style);
|
|
26
|
-
const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
27
|
+
const { hasPositionFixed, hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
27
28
|
const nodeRef = useRef(null);
|
|
28
29
|
useNodesRef(props, ref, nodeRef, { style: normalStyle });
|
|
29
30
|
const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef });
|
|
@@ -77,10 +78,16 @@ const radioGroup = forwardRef((props, ref) => {
|
|
|
77
78
|
}), ['name'], {
|
|
78
79
|
layoutRef
|
|
79
80
|
});
|
|
80
|
-
|
|
81
|
+
const finalComponent = createElement(View, innerProps, createElement(RadioGroupContext.Provider, {
|
|
82
|
+
value: contextValue
|
|
83
|
+
}, wrapChildren(props, {
|
|
81
84
|
hasVarDec,
|
|
82
85
|
varContext: varContextRef.current
|
|
83
86
|
})));
|
|
87
|
+
if (hasPositionFixed) {
|
|
88
|
+
return createElement(Portal, null, finalComponent);
|
|
89
|
+
}
|
|
90
|
+
return finalComponent;
|
|
84
91
|
});
|
|
85
92
|
radioGroup.displayName = 'MpxRadioGroup';
|
|
86
93
|
export default radioGroup;
|
|
@@ -12,6 +12,7 @@ import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
|
12
12
|
import useNodesRef from './useNodesRef';
|
|
13
13
|
import { splitProps, splitStyle, useLayout, useTransformStyle, wrapChildren, extendObject } from './utils';
|
|
14
14
|
import Icon from './mpx-icon';
|
|
15
|
+
import Portal from './mpx-portal';
|
|
15
16
|
const styles = StyleSheet.create({
|
|
16
17
|
container: {
|
|
17
18
|
flexDirection: 'row',
|
|
@@ -73,7 +74,7 @@ const Radio = forwardRef((radioProps, ref) => {
|
|
|
73
74
|
bindtap && bindtap(getCustomEvent('tap', evt, { layoutRef }, props));
|
|
74
75
|
onChange(evt);
|
|
75
76
|
};
|
|
76
|
-
const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
77
|
+
const { hasPositionFixed, hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
77
78
|
const { textStyle, backgroundStyle, innerStyle = {} } = splitStyle(normalStyle);
|
|
78
79
|
if (backgroundStyle) {
|
|
79
80
|
warn('Radio does not support background image-related styles!');
|
|
@@ -123,7 +124,7 @@ const Radio = forwardRef((radioProps, ref) => {
|
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
}, [checked]);
|
|
126
|
-
|
|
127
|
+
const finalComponent = createElement(View, innerProps, createElement(View, { style: defaultStyle }, createElement(Icon, {
|
|
127
128
|
type: 'success',
|
|
128
129
|
size: 24,
|
|
129
130
|
color: disabled ? '#E1E1E1' : color,
|
|
@@ -134,6 +135,10 @@ const Radio = forwardRef((radioProps, ref) => {
|
|
|
134
135
|
textStyle,
|
|
135
136
|
textProps
|
|
136
137
|
}));
|
|
138
|
+
if (hasPositionFixed) {
|
|
139
|
+
return createElement(Portal, null, finalComponent);
|
|
140
|
+
}
|
|
141
|
+
return finalComponent;
|
|
137
142
|
});
|
|
138
143
|
Radio.displayName = 'MpxRadio';
|
|
139
144
|
export default Radio;
|
|
@@ -8,6 +8,7 @@ import useNodesRef from '../useNodesRef'; // 引入辅助函数
|
|
|
8
8
|
import { useTransformStyle, useLayout, extendObject } from '../utils';
|
|
9
9
|
import { WebView } from 'react-native-webview';
|
|
10
10
|
import { generateHTML } from './html';
|
|
11
|
+
import Portal from '../mpx-portal';
|
|
11
12
|
function jsonToHtmlStr(elements) {
|
|
12
13
|
let htmlStr = '';
|
|
13
14
|
for (const element of elements) {
|
|
@@ -30,7 +31,7 @@ const _RichText = forwardRef((props, ref) => {
|
|
|
30
31
|
const { style = {}, nodes, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
31
32
|
const nodeRef = useRef(null);
|
|
32
33
|
const [webViewHeight, setWebViewHeight] = useState(0);
|
|
33
|
-
const { normalStyle, hasSelfPercent, setWidth, setHeight } = useTransformStyle(Object.assign({
|
|
34
|
+
const { normalStyle, hasSelfPercent, setWidth, setHeight, hasPositionFixed } = useTransformStyle(Object.assign({
|
|
34
35
|
width: '100%',
|
|
35
36
|
height: webViewHeight
|
|
36
37
|
}, style), {
|
|
@@ -51,12 +52,19 @@ const _RichText = forwardRef((props, ref) => {
|
|
|
51
52
|
layoutRef
|
|
52
53
|
});
|
|
53
54
|
const html = typeof nodes === 'string' ? nodes : jsonToHtmlStr(nodes);
|
|
54
|
-
|
|
55
|
+
let finalComponent = createElement(View, innerProps, createElement(WebView, {
|
|
55
56
|
source: { html: generateHTML(html) },
|
|
56
57
|
onMessage: (event) => {
|
|
57
58
|
setWebViewHeight(+event.nativeEvent.data);
|
|
59
|
+
},
|
|
60
|
+
style: {
|
|
61
|
+
backgroundColor: 'transparent'
|
|
58
62
|
}
|
|
59
63
|
}));
|
|
64
|
+
if (hasPositionFixed) {
|
|
65
|
+
finalComponent = createElement(Portal, null, finalComponent);
|
|
66
|
+
}
|
|
67
|
+
return finalComponent;
|
|
60
68
|
});
|
|
61
69
|
_RichText.displayName = 'mpx-rich-text';
|
|
62
70
|
export default _RichText;
|
|
@@ -34,12 +34,13 @@
|
|
|
34
34
|
import { ScrollView, RefreshControl, Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
35
35
|
import { Animated as RNAnimated } from 'react-native';
|
|
36
36
|
import { isValidElement, Children, useRef, useState, useEffect, forwardRef, useContext, useMemo, createElement } from 'react';
|
|
37
|
-
import Animated, {
|
|
38
|
-
import { warn } from '@mpxjs/utils';
|
|
37
|
+
import Animated, { useSharedValue, withTiming, useAnimatedStyle, runOnJS } from 'react-native-reanimated';
|
|
38
|
+
import { warn, hasOwn } from '@mpxjs/utils';
|
|
39
39
|
import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
40
40
|
import useNodesRef from './useNodesRef';
|
|
41
|
-
import { splitProps, splitStyle, useTransformStyle, useLayout, wrapChildren, extendObject, flatGesture, HIDDEN_STYLE } from './utils';
|
|
41
|
+
import { splitProps, splitStyle, useTransformStyle, useLayout, wrapChildren, extendObject, flatGesture, HIDDEN_STYLE, useRunOnJSCallback } from './utils';
|
|
42
42
|
import { IntersectionObserverContext, ScrollViewContext } from './context';
|
|
43
|
+
import Portal from './mpx-portal';
|
|
43
44
|
const AnimatedScrollView = RNAnimated.createAnimatedComponent(ScrollView);
|
|
44
45
|
const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
45
46
|
const { textProps, innerProps: props = {} } = splitProps(scrollViewProps);
|
|
@@ -73,11 +74,29 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
73
74
|
black: ['#000'],
|
|
74
75
|
white: ['#fff']
|
|
75
76
|
};
|
|
77
|
+
const isContentSizeChange = useRef(false);
|
|
76
78
|
const { refresherContent, otherContent } = getRefresherContent(props.children);
|
|
77
79
|
const hasRefresher = refresherContent && refresherEnabled;
|
|
78
|
-
const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
80
|
+
const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, hasPositionFixed, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
79
81
|
const { textStyle, innerStyle = {} } = splitStyle(normalStyle);
|
|
80
|
-
const scrollViewRef =
|
|
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
|
+
};
|
|
93
|
+
const runOnJSCallbackRef = useRef({
|
|
94
|
+
setEnableScroll,
|
|
95
|
+
setScrollBounces,
|
|
96
|
+
setRefreshing,
|
|
97
|
+
onRefresh
|
|
98
|
+
});
|
|
99
|
+
const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef);
|
|
81
100
|
useNodesRef(props, ref, scrollViewRef, {
|
|
82
101
|
style: normalStyle,
|
|
83
102
|
scrollOffset: scrollOptions,
|
|
@@ -205,7 +224,22 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
205
224
|
}
|
|
206
225
|
}
|
|
207
226
|
function onContentSizeChange(width, height) {
|
|
208
|
-
|
|
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
|
+
}
|
|
209
243
|
}
|
|
210
244
|
function onLayout(e) {
|
|
211
245
|
const layout = e.nativeEvent.layout || {};
|
|
@@ -225,8 +259,9 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
225
259
|
}
|
|
226
260
|
function onScroll(e) {
|
|
227
261
|
const { bindscroll } = props;
|
|
228
|
-
const {
|
|
229
|
-
const {
|
|
262
|
+
const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
|
|
263
|
+
const { x: scrollLeft, y: scrollTop } = contentOffset;
|
|
264
|
+
const { width: scrollWidth, height: scrollHeight } = contentSize;
|
|
230
265
|
isAtTop.value = scrollTop <= 0;
|
|
231
266
|
bindscroll &&
|
|
232
267
|
bindscroll(getCustomEvent('scroll', e, {
|
|
@@ -236,7 +271,8 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
236
271
|
scrollHeight,
|
|
237
272
|
scrollWidth,
|
|
238
273
|
deltaX: scrollLeft - scrollOptions.current.scrollLeft,
|
|
239
|
-
deltaY: scrollTop - scrollOptions.current.scrollTop
|
|
274
|
+
deltaY: scrollTop - scrollOptions.current.scrollTop,
|
|
275
|
+
layoutMeasurement
|
|
240
276
|
},
|
|
241
277
|
layoutRef
|
|
242
278
|
}, props));
|
|
@@ -249,8 +285,9 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
249
285
|
}
|
|
250
286
|
function onScrollEnd(e) {
|
|
251
287
|
const { bindscrollend } = props;
|
|
252
|
-
const {
|
|
253
|
-
const {
|
|
288
|
+
const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
|
|
289
|
+
const { x: scrollLeft, y: scrollTop } = contentOffset;
|
|
290
|
+
const { width: scrollWidth, height: scrollHeight } = contentSize;
|
|
254
291
|
isAtTop.value = scrollTop <= 0;
|
|
255
292
|
bindscrollend &&
|
|
256
293
|
bindscrollend(getCustomEvent('scrollend', e, {
|
|
@@ -258,7 +295,8 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
258
295
|
scrollLeft,
|
|
259
296
|
scrollTop,
|
|
260
297
|
scrollHeight,
|
|
261
|
-
scrollWidth
|
|
298
|
+
scrollWidth,
|
|
299
|
+
layoutMeasurement
|
|
262
300
|
},
|
|
263
301
|
layoutRef
|
|
264
302
|
}, props));
|
|
@@ -284,20 +322,6 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
284
322
|
snapScrollTop.current = y;
|
|
285
323
|
}
|
|
286
324
|
}
|
|
287
|
-
function onScrollTouchStart(e) {
|
|
288
|
-
const { bindtouchstart } = props;
|
|
289
|
-
bindtouchstart && bindtouchstart(e);
|
|
290
|
-
if (enhanced) {
|
|
291
|
-
binddragstart &&
|
|
292
|
-
binddragstart(getCustomEvent('dragstart', e, {
|
|
293
|
-
detail: {
|
|
294
|
-
scrollLeft: scrollOptions.current.scrollLeft,
|
|
295
|
-
scrollTop: scrollOptions.current.scrollTop
|
|
296
|
-
},
|
|
297
|
-
layoutRef
|
|
298
|
-
}, props));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
325
|
function onScrollTouchMove(e) {
|
|
302
326
|
bindtouchmove && bindtouchmove(e);
|
|
303
327
|
if (enhanced) {
|
|
@@ -311,19 +335,6 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
311
335
|
}, props));
|
|
312
336
|
}
|
|
313
337
|
}
|
|
314
|
-
function onScrollTouchEnd(e) {
|
|
315
|
-
bindtouchend && bindtouchend(e);
|
|
316
|
-
if (enhanced) {
|
|
317
|
-
binddragend &&
|
|
318
|
-
binddragend(getCustomEvent('dragend', e, {
|
|
319
|
-
detail: {
|
|
320
|
-
scrollLeft: scrollOptions.current.scrollLeft || 0,
|
|
321
|
-
scrollTop: scrollOptions.current.scrollTop || 0
|
|
322
|
-
},
|
|
323
|
-
layoutRef
|
|
324
|
-
}, props));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
338
|
function onScrollDrag(e) {
|
|
328
339
|
const { x: scrollLeft, y: scrollTop } = e.nativeEvent.contentOffset;
|
|
329
340
|
updateScrollOptions(e, { scrollLeft, scrollTop });
|
|
@@ -332,6 +343,16 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
332
343
|
const scrollHandler = RNAnimated.event([{ nativeEvent: { contentOffset: { y: scrollOffset } } }], {
|
|
333
344
|
useNativeDriver: true,
|
|
334
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
|
+
}
|
|
335
356
|
onScroll(event);
|
|
336
357
|
}
|
|
337
358
|
});
|
|
@@ -339,9 +360,34 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
339
360
|
hasCallScrollToLower.current = false;
|
|
340
361
|
hasCallScrollToUpper.current = false;
|
|
341
362
|
onScrollDrag(e);
|
|
363
|
+
if (enhanced) {
|
|
364
|
+
binddragstart &&
|
|
365
|
+
binddragstart(getCustomEvent('dragstart', e, {
|
|
366
|
+
detail: {
|
|
367
|
+
scrollLeft: scrollOptions.current.scrollLeft,
|
|
368
|
+
scrollTop: scrollOptions.current.scrollTop
|
|
369
|
+
},
|
|
370
|
+
layoutRef
|
|
371
|
+
}, props));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function onScrollDragEnd(e) {
|
|
375
|
+
onScrollDrag(e);
|
|
376
|
+
if (enhanced) {
|
|
377
|
+
// 安卓上如果触发了默认的下拉刷新,binddragend可能不触发,只会触发 binddragstart
|
|
378
|
+
binddragend &&
|
|
379
|
+
binddragend(getCustomEvent('dragend', e, {
|
|
380
|
+
detail: {
|
|
381
|
+
scrollLeft: scrollOptions.current.scrollLeft || 0,
|
|
382
|
+
scrollTop: scrollOptions.current.scrollTop || 0
|
|
383
|
+
},
|
|
384
|
+
layoutRef
|
|
385
|
+
}, props));
|
|
386
|
+
}
|
|
342
387
|
}
|
|
343
388
|
// 处理刷新
|
|
344
389
|
function onRefresh() {
|
|
390
|
+
const { hasRefresher, refresherTriggered } = refresherStateRef.current;
|
|
345
391
|
if (hasRefresher && refresherTriggered === undefined) {
|
|
346
392
|
// 处理使用了自定义刷新组件,又没设置 refresherTriggered 的情况
|
|
347
393
|
setRefreshing(true);
|
|
@@ -353,9 +399,9 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
353
399
|
}
|
|
354
400
|
}, 500);
|
|
355
401
|
}
|
|
356
|
-
const { bindrefresherrefresh } =
|
|
402
|
+
const { bindrefresherrefresh } = propsRef.current;
|
|
357
403
|
bindrefresherrefresh &&
|
|
358
|
-
bindrefresherrefresh(getCustomEvent('refresherrefresh', {}, { layoutRef },
|
|
404
|
+
bindrefresherrefresh(getCustomEvent('refresherrefresh', {}, { layoutRef }, propsRef.current));
|
|
359
405
|
}
|
|
360
406
|
function getRefresherContent(children) {
|
|
361
407
|
let refresherContent = null;
|
|
@@ -403,7 +449,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
403
449
|
'worklet';
|
|
404
450
|
if (enableScrollValue.value !== newValue) {
|
|
405
451
|
enableScrollValue.value = newValue;
|
|
406
|
-
runOnJS(
|
|
452
|
+
runOnJS(runOnJSCallback)('setEnableScroll', newValue);
|
|
407
453
|
}
|
|
408
454
|
}
|
|
409
455
|
const resetScrollState = (value) => {
|
|
@@ -414,7 +460,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
414
460
|
'worklet';
|
|
415
461
|
if (bouncesValue.value !== newValue) {
|
|
416
462
|
bouncesValue.value = newValue;
|
|
417
|
-
runOnJS(
|
|
463
|
+
runOnJS(runOnJSCallback)('setScrollBounces', newValue);
|
|
418
464
|
}
|
|
419
465
|
}
|
|
420
466
|
// 处理下拉刷新的手势
|
|
@@ -459,7 +505,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
459
505
|
if ((event.translationY > 0 && translateY.value < refresherThreshold) || event.translationY < 0) {
|
|
460
506
|
translateY.value = withTiming(0);
|
|
461
507
|
updateScrollState(true);
|
|
462
|
-
runOnJS(
|
|
508
|
+
runOnJS(runOnJSCallback)('setRefreshing', false);
|
|
463
509
|
}
|
|
464
510
|
else {
|
|
465
511
|
translateY.value = withTiming(refresherHeight.value);
|
|
@@ -468,18 +514,22 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
468
514
|
else if (event.translationY >= refresherHeight.value) {
|
|
469
515
|
// 触发刷新
|
|
470
516
|
translateY.value = withTiming(refresherHeight.value);
|
|
471
|
-
runOnJS(
|
|
517
|
+
runOnJS(runOnJSCallback)('onRefresh');
|
|
472
518
|
}
|
|
473
519
|
else {
|
|
474
520
|
// 回弹
|
|
475
521
|
translateY.value = withTiming(0);
|
|
476
522
|
updateScrollState(true);
|
|
477
|
-
runOnJS(
|
|
523
|
+
runOnJS(runOnJSCallback)('setRefreshing', false);
|
|
478
524
|
}
|
|
479
525
|
})
|
|
480
526
|
.simultaneousWithExternalGesture(scrollViewRef);
|
|
481
527
|
const scrollAdditionalProps = extendObject({
|
|
482
|
-
style: extendObject(
|
|
528
|
+
style: extendObject(hasOwn(innerStyle, 'flex') || hasOwn(innerStyle, 'flexGrow')
|
|
529
|
+
? {}
|
|
530
|
+
: {
|
|
531
|
+
flexGrow: 0
|
|
532
|
+
}, innerStyle, layoutStyle),
|
|
483
533
|
pinchGestureEnabled: false,
|
|
484
534
|
alwaysBounceVertical: false,
|
|
485
535
|
alwaysBounceHorizontal: false,
|
|
@@ -490,14 +540,13 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
490
540
|
showsVerticalScrollIndicator: scrollY && showScrollbar,
|
|
491
541
|
scrollEnabled: !enableScroll ? false : !!(scrollX || scrollY),
|
|
492
542
|
bounces: false,
|
|
543
|
+
overScrollMode: 'never',
|
|
493
544
|
ref: scrollViewRef,
|
|
494
545
|
onScroll: enableSticky ? scrollHandler : onScroll,
|
|
495
546
|
onContentSizeChange: onContentSizeChange,
|
|
496
|
-
bindtouchstart: ((enhanced && binddragstart) || bindtouchstart) && onScrollTouchStart,
|
|
497
547
|
bindtouchmove: ((enhanced && binddragging) || bindtouchmove) && onScrollTouchMove,
|
|
498
|
-
bindtouchend: ((enhanced && binddragend) || bindtouchend) && onScrollTouchEnd,
|
|
499
548
|
onScrollBeginDrag: onScrollDragStart,
|
|
500
|
-
onScrollEndDrag:
|
|
549
|
+
onScrollEndDrag: onScrollDragEnd,
|
|
501
550
|
onMomentumScrollEnd: onScrollEnd
|
|
502
551
|
}, (simultaneousHandlers ? { simultaneousHandlers } : {}), (waitForHandlers ? { waitFor: waitForHandlers } : {}), layoutProps);
|
|
503
552
|
if (enhanced) {
|
|
@@ -556,7 +605,11 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
556
605
|
textStyle,
|
|
557
606
|
textProps
|
|
558
607
|
})));
|
|
559
|
-
|
|
608
|
+
let scrollViewComponent = hasRefresher ? withRefresherScrollView : commonScrollView;
|
|
609
|
+
if (hasPositionFixed) {
|
|
610
|
+
scrollViewComponent = createElement(Portal, null, scrollViewComponent);
|
|
611
|
+
}
|
|
612
|
+
return scrollViewComponent;
|
|
560
613
|
});
|
|
561
614
|
_ScrollView.displayName = 'MpxScrollView';
|
|
562
615
|
export default _ScrollView;
|