@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.
Files changed (36) hide show
  1. package/lib/runtime/components/react/dist/getInnerListeners.js +36 -22
  2. package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +145 -0
  3. package/lib/runtime/components/react/dist/mpx-button.jsx +7 -2
  4. package/lib/runtime/components/react/dist/mpx-canvas/Image.js +2 -4
  5. package/lib/runtime/components/react/dist/mpx-canvas/index.jsx +20 -17
  6. package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +7 -2
  7. package/lib/runtime/components/react/dist/mpx-checkbox.jsx +7 -2
  8. package/lib/runtime/components/react/dist/mpx-icon/index.jsx +7 -2
  9. package/lib/runtime/components/react/dist/mpx-image.jsx +33 -20
  10. package/lib/runtime/components/react/dist/mpx-input.jsx +7 -2
  11. package/lib/runtime/components/react/dist/mpx-label.jsx +7 -2
  12. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +8 -3
  13. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +205 -79
  14. package/lib/runtime/components/react/dist/mpx-picker/index.jsx +11 -13
  15. package/lib/runtime/components/react/dist/mpx-picker-view/index.jsx +8 -7
  16. package/lib/runtime/components/react/dist/mpx-picker-view-column/index.jsx +29 -11
  17. package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +3 -5
  18. package/lib/runtime/components/react/dist/mpx-progress.jsx +163 -0
  19. package/lib/runtime/components/react/dist/mpx-radio-group.jsx +9 -2
  20. package/lib/runtime/components/react/dist/mpx-radio.jsx +7 -2
  21. package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +10 -2
  22. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +104 -51
  23. package/lib/runtime/components/react/dist/mpx-slider.jsx +321 -0
  24. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +3 -1
  25. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +11 -9
  26. package/lib/runtime/components/react/dist/mpx-swiper.jsx +203 -141
  27. package/lib/runtime/components/react/dist/mpx-switch.jsx +7 -2
  28. package/lib/runtime/components/react/dist/mpx-text.jsx +7 -2
  29. package/lib/runtime/components/react/dist/mpx-video.jsx +7 -2
  30. package/lib/runtime/components/react/dist/mpx-view.jsx +28 -26
  31. package/lib/runtime/components/react/dist/mpx-web-view.jsx +34 -29
  32. package/lib/runtime/components/react/dist/useAnimationHooks.js +12 -89
  33. package/lib/runtime/components/react/dist/utils.jsx +199 -114
  34. package/lib/runtime/components/react/mpx-input.tsx +6 -6
  35. package/lib/template-compiler/compiler.js +1 -1
  36. 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
- return createElement(View, innerProps, createElement(RadioGroupContext.Provider, { value: contextValue }, wrapChildren(props, {
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
- return createElement(View, innerProps, createElement(View, { style: defaultStyle }, createElement(Icon, {
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
- return createElement(View, innerProps, createElement(WebView, {
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, { useAnimatedRef, useSharedValue, withTiming, useAnimatedStyle, runOnJS } from 'react-native-reanimated';
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 = useAnimatedRef();
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
- scrollOptions.current.contentLength = selectLength({ height, width });
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 { x: scrollLeft, y: scrollTop } = e.nativeEvent.contentOffset;
229
- const { width: scrollWidth, height: scrollHeight } = e.nativeEvent.contentSize;
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 { x: scrollLeft, y: scrollTop } = e.nativeEvent.contentOffset;
253
- const { width: scrollWidth, height: scrollHeight } = e.nativeEvent.contentSize;
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 } = props;
402
+ const { bindrefresherrefresh } = propsRef.current;
357
403
  bindrefresherrefresh &&
358
- bindrefresherrefresh(getCustomEvent('refresherrefresh', {}, { layoutRef }, props));
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(setEnableScroll)(newValue);
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(setScrollBounces)(newValue);
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(setRefreshing)(false);
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(onRefresh)();
517
+ runOnJS(runOnJSCallback)('onRefresh');
472
518
  }
473
519
  else {
474
520
  // 回弹
475
521
  translateY.value = withTiming(0);
476
522
  updateScrollState(true);
477
- runOnJS(setRefreshing)(false);
523
+ runOnJS(runOnJSCallback)('setRefreshing', false);
478
524
  }
479
525
  })
480
526
  .simultaneousWithExternalGesture(scrollViewRef);
481
527
  const scrollAdditionalProps = extendObject({
482
- style: extendObject({}, innerStyle, layoutStyle),
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: onScrollDrag,
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
- return hasRefresher ? withRefresherScrollView : commonScrollView;
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;