@mpxjs/webpack-plugin 2.10.3-beta.17 → 2.10.3-beta.18

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 (68) hide show
  1. package/lib/runtime/components/react/dist/context.js +5 -1
  2. package/lib/runtime/components/react/dist/event.config.js +0 -1
  3. package/lib/runtime/components/react/dist/getInnerListeners.js +148 -149
  4. package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +145 -0
  5. package/lib/runtime/components/react/dist/mpx-button.jsx +11 -7
  6. package/lib/runtime/components/react/dist/mpx-canvas/Image.js +2 -4
  7. package/lib/runtime/components/react/dist/mpx-canvas/index.jsx +23 -21
  8. package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +9 -4
  9. package/lib/runtime/components/react/dist/mpx-checkbox.jsx +9 -5
  10. package/lib/runtime/components/react/dist/mpx-form.jsx +2 -2
  11. package/lib/runtime/components/react/dist/mpx-icon/icons/cancel.png +0 -0
  12. package/lib/runtime/components/react/dist/mpx-icon/icons/clear.png +0 -0
  13. package/lib/runtime/components/react/dist/mpx-icon/icons/download.png +0 -0
  14. package/lib/runtime/components/react/dist/mpx-icon/icons/info.png +0 -0
  15. package/lib/runtime/components/react/dist/mpx-icon/icons/search.png +0 -0
  16. package/lib/runtime/components/react/dist/mpx-icon/icons/success.png +0 -0
  17. package/lib/runtime/components/react/dist/mpx-icon/icons/success_no_circle.png +0 -0
  18. package/lib/runtime/components/react/dist/mpx-icon/icons/waiting.png +0 -0
  19. package/lib/runtime/components/react/dist/mpx-icon/icons/warn.png +0 -0
  20. package/lib/runtime/components/react/dist/mpx-icon/index.jsx +9 -4
  21. package/lib/runtime/components/react/dist/mpx-image.jsx +92 -41
  22. package/lib/runtime/components/react/dist/mpx-inline-text.jsx +11 -0
  23. package/lib/runtime/components/react/dist/mpx-input.jsx +14 -13
  24. package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +22 -7
  25. package/lib/runtime/components/react/dist/mpx-label.jsx +9 -5
  26. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +10 -5
  27. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +206 -80
  28. package/lib/runtime/components/react/dist/mpx-navigator.jsx +11 -3
  29. package/lib/runtime/components/react/dist/mpx-picker/date.jsx +194 -68
  30. package/lib/runtime/components/react/dist/mpx-picker/dateData.js +17 -0
  31. package/lib/runtime/components/react/dist/mpx-picker/index.jsx +178 -98
  32. package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +79 -139
  33. package/lib/runtime/components/react/dist/mpx-picker/region.jsx +190 -90
  34. package/lib/runtime/components/react/dist/mpx-picker/selector.jsx +60 -75
  35. package/lib/runtime/components/react/dist/mpx-picker/time.jsx +100 -228
  36. package/lib/runtime/components/react/dist/{mpx-picker-view.jsx → mpx-picker-view/index.jsx} +16 -15
  37. package/lib/runtime/components/react/dist/{mpx-picker-view-column.jsx → mpx-picker-view-column/index.jsx} +95 -26
  38. package/lib/runtime/components/react/dist/{mpx-picker-view-column-item.jsx → mpx-picker-view-column/pickerViewColumnItem.jsx} +16 -16
  39. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItemLite.jsx +20 -0
  40. package/lib/runtime/components/react/dist/{pickerFaces.js → mpx-picker-view-column/pickerViewFaces.js} +6 -0
  41. package/lib/runtime/components/react/dist/mpx-popup/index.jsx +61 -0
  42. package/lib/runtime/components/react/dist/mpx-popup/popupBase.jsx +92 -0
  43. package/lib/runtime/components/react/dist/mpx-portal/index.jsx +5 -1
  44. package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +3 -5
  45. package/lib/runtime/components/react/dist/mpx-progress.jsx +163 -0
  46. package/lib/runtime/components/react/dist/mpx-radio-group.jsx +11 -4
  47. package/lib/runtime/components/react/dist/mpx-radio.jsx +9 -5
  48. package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +12 -4
  49. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +317 -89
  50. package/lib/runtime/components/react/dist/mpx-simple-text.jsx +7 -5
  51. package/lib/runtime/components/react/dist/mpx-simple-view.jsx +11 -15
  52. package/lib/runtime/components/react/dist/mpx-slider.jsx +321 -0
  53. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +117 -0
  54. package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +45 -0
  55. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +15 -14
  56. package/lib/runtime/components/react/dist/mpx-swiper.jsx +245 -121
  57. package/lib/runtime/components/react/dist/mpx-switch.jsx +10 -7
  58. package/lib/runtime/components/react/dist/mpx-text.jsx +43 -13
  59. package/lib/runtime/components/react/dist/mpx-video.jsx +12 -7
  60. package/lib/runtime/components/react/dist/mpx-view.jsx +34 -18
  61. package/lib/runtime/components/react/dist/mpx-web-view.jsx +40 -35
  62. package/lib/runtime/components/react/dist/useAnimationHooks.js +35 -90
  63. package/lib/runtime/components/react/dist/utils.jsx +215 -109
  64. package/lib/runtime/components/web/mpx-titlebar.vue +21 -18
  65. package/package.json +1 -1
  66. /package/lib/runtime/components/react/dist/{pickerVIewContext.js → mpx-picker-view/pickerVIewContext.js} +0 -0
  67. /package/lib/runtime/components/react/dist/{pickerViewIndicator.jsx → mpx-picker-view-column/pickerViewIndicator.jsx} +0 -0
  68. /package/lib/runtime/components/react/dist/{pickerViewMask.jsx → mpx-picker-view-column/pickerViewMask.jsx} +0 -0
@@ -1,22 +1,31 @@
1
- import React, { useContext, useEffect } from 'react';
2
- import { Keyboard, View, Platform } from 'react-native';
3
- import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
1
+ import React, { useContext, useEffect, useRef } from 'react';
2
+ import { Keyboard, View } from 'react-native';
3
+ import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing, cancelAnimation } from 'react-native-reanimated';
4
4
  import { KeyboardAvoidContext } from './context';
5
+ import { isIOS } from './utils';
5
6
  const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
6
- const isIOS = Platform.OS === 'ios';
7
7
  const duration = isIOS ? 250 : 300;
8
8
  const easing = isIOS ? Easing.inOut(Easing.ease) : Easing.out(Easing.quad);
9
9
  const offset = useSharedValue(0);
10
10
  const basic = useSharedValue('auto');
11
11
  const keyboardAvoid = useContext(KeyboardAvoidContext);
12
+ // fix: 某些特殊机型下隐藏键盘可能会先触发一次 keyboardWillShow,
13
+ // 比如机型 iPhone 11 Pro,可能会导致显隐动画冲突
14
+ // 因此增加状态标记 + cancelAnimation 来优化
15
+ const isShow = useRef(false);
12
16
  const animatedStyle = useAnimatedStyle(() => ({
13
17
  transform: [{ translateY: -offset.value }],
14
18
  flexBasis: basic.value
15
19
  }));
16
20
  const resetKeyboard = () => {
21
+ if (!isShow.current) {
22
+ return;
23
+ }
24
+ isShow.current = false;
17
25
  if (keyboardAvoid?.current) {
18
26
  keyboardAvoid.current = null;
19
27
  }
28
+ cancelAnimation(offset);
20
29
  offset.value = withTiming(0, { duration, easing });
21
30
  basic.value = 'auto';
22
31
  };
@@ -30,16 +39,19 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
30
39
  if (isIOS) {
31
40
  subscriptions = [
32
41
  Keyboard.addListener('keyboardWillShow', (evt) => {
33
- if (!keyboardAvoid?.current)
42
+ if (!keyboardAvoid?.current || isShow.current) {
34
43
  return;
44
+ }
45
+ isShow.current = true;
35
46
  const { endCoordinates } = evt;
36
47
  const { ref, cursorSpacing = 0 } = keyboardAvoid.current;
37
48
  setTimeout(() => {
38
49
  ref?.current?.measure((x, y, width, height, pageX, pageY) => {
39
- const aboveOffset = pageY + height - endCoordinates.screenY;
50
+ const aboveOffset = offset.value + pageY + height - endCoordinates.screenY;
40
51
  const aboveValue = -aboveOffset >= cursorSpacing ? 0 : aboveOffset + cursorSpacing;
41
52
  const belowValue = Math.min(endCoordinates.height, aboveOffset + cursorSpacing);
42
53
  const value = aboveOffset > 0 ? belowValue : aboveValue;
54
+ cancelAnimation(offset);
43
55
  offset.value = withTiming(value, { duration, easing }, (finished) => {
44
56
  if (finished) {
45
57
  // Set flexBasic after animation to trigger re-layout and reset layout information
@@ -55,8 +67,10 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
55
67
  else {
56
68
  subscriptions = [
57
69
  Keyboard.addListener('keyboardDidShow', (evt) => {
58
- if (!keyboardAvoid?.current)
70
+ if (!keyboardAvoid?.current || isShow.current) {
59
71
  return;
72
+ }
73
+ isShow.current = true;
60
74
  const { endCoordinates } = evt;
61
75
  const { ref, cursorSpacing = 0 } = keyboardAvoid.current;
62
76
  ref?.current?.measure((x, y, width, height, pageX, pageY) => {
@@ -65,6 +79,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
65
79
  const aboveValue = -aboveOffset >= cursorSpacing ? 0 : aboveOffset + cursorSpacing;
66
80
  const belowValue = Math.min(belowOffset, cursorSpacing);
67
81
  const value = aboveOffset > 0 ? belowValue : aboveValue;
82
+ cancelAnimation(offset);
68
83
  offset.value = withTiming(value, { duration, easing }, (finished) => {
69
84
  if (finished) {
70
85
  // Set flexBasic after animation to trigger re-layout and reset layout information
@@ -8,6 +8,7 @@ import useInnerProps, { getCustomEvent } from './getInnerListeners';
8
8
  import useNodesRef from './useNodesRef';
9
9
  import { splitProps, splitStyle, useLayout, useTransformStyle, wrapChildren, extendObject } from './utils';
10
10
  import { LabelContext } from './context';
11
+ import Portal from './mpx-portal';
11
12
  const Label = forwardRef((labelProps, ref) => {
12
13
  const { textProps, innerProps: props = {} } = splitProps(labelProps);
13
14
  const propsRef = useRef({});
@@ -17,7 +18,7 @@ const Label = forwardRef((labelProps, ref) => {
17
18
  flexDirection: 'row'
18
19
  };
19
20
  const styleObj = extendObject({}, defaultStyle, style);
20
- const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
21
+ const { hasPositionFixed, hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
21
22
  const nodeRef = useRef(null);
22
23
  useNodesRef(props, ref, nodeRef, { style: normalStyle });
23
24
  const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef });
@@ -33,20 +34,23 @@ const Label = forwardRef((labelProps, ref) => {
33
34
  bindtap && bindtap(getCustomEvent('tap', evt, { layoutRef }, { props: propsRef.current }));
34
35
  contextRef.current.triggerChange(evt);
35
36
  }, []);
36
- const innerProps = useInnerProps(props, extendObject({
37
+ const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
37
38
  ref: nodeRef,
38
- style: extendObject({}, innerStyle, layoutStyle)
39
- }, layoutProps, {
39
+ style: extendObject({}, innerStyle, layoutStyle),
40
40
  bindtap: onTap
41
41
  }), [], {
42
42
  layoutRef
43
43
  });
44
- return createElement(View, innerProps, createElement(LabelContext.Provider, { value: contextRef }, wrapChildren(props, {
44
+ const finalComponent = createElement(View, innerProps, createElement(LabelContext.Provider, { value: contextRef }, wrapChildren(props, {
45
45
  hasVarDec,
46
46
  varContext: varContextRef.current,
47
47
  textStyle,
48
48
  textProps
49
49
  })));
50
+ if (hasPositionFixed) {
51
+ return createElement(Portal, null, finalComponent);
52
+ }
53
+ return finalComponent;
50
54
  });
51
55
  Label.displayName = 'MpxLabel';
52
56
  export default Label;
@@ -7,9 +7,10 @@ import useNodesRef from './useNodesRef';
7
7
  import useInnerProps from './getInnerListeners';
8
8
  import { MovableAreaContext } from './context';
9
9
  import { useTransformStyle, wrapChildren, useLayout, extendObject } from './utils';
10
+ import Portal from './mpx-portal';
10
11
  const _MovableArea = forwardRef((props, ref) => {
11
12
  const { style = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
12
- const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
13
+ const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, hasPositionFixed, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
13
14
  const movableViewRef = useRef(null);
14
15
  useNodesRef(props, ref, movableViewRef, {
15
16
  style: normalStyle
@@ -19,14 +20,18 @@ const _MovableArea = forwardRef((props, ref) => {
19
20
  width: normalStyle.width || 10
20
21
  }), [normalStyle.width, normalStyle.height]);
21
22
  const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: movableViewRef });
22
- const innerProps = useInnerProps(props, extendObject({
23
- style: extendObject({ height: contextValue.height, width: contextValue.width, overflow: 'hidden' }, normalStyle, layoutStyle),
23
+ const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
24
+ style: extendObject({ height: contextValue.height, width: contextValue.width }, normalStyle, layoutStyle),
24
25
  ref: movableViewRef
25
- }, layoutProps), [], { layoutRef });
26
- return createElement(MovableAreaContext.Provider, { value: contextValue }, createElement(View, innerProps, wrapChildren(props, {
26
+ }), [], { layoutRef });
27
+ let movableComponent = createElement(MovableAreaContext.Provider, { value: contextValue }, createElement(View, innerProps, wrapChildren(props, {
27
28
  hasVarDec,
28
29
  varContext: varContextRef.current
29
30
  })));
31
+ if (hasPositionFixed) {
32
+ movableComponent = createElement(Portal, null, movableComponent);
33
+ }
34
+ return movableComponent;
30
35
  });
31
36
  _MovableArea.displayName = 'MpxMovableArea';
32
37
  export default _MovableArea;
@@ -4,8 +4,8 @@
4
4
  * ✔ out-of-bounds
5
5
  * ✔ x
6
6
  * ✔ y
7
- * damping
8
- * friction
7
+ * damping
8
+ * friction
9
9
  * ✔ disabled
10
10
  * ✘ scale
11
11
  * ✘ scale-min
@@ -22,10 +22,98 @@ import { StyleSheet } from 'react-native';
22
22
  import useInnerProps, { getCustomEvent } from './getInnerListeners';
23
23
  import useNodesRef from './useNodesRef';
24
24
  import { MovableAreaContext } from './context';
25
- import { useTransformStyle, splitProps, splitStyle, HIDDEN_STYLE, wrapChildren, flatGesture, extendObject, omit, useNavigation } from './utils';
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, withDecay, runOnJS, runOnUI, useAnimatedReaction, withSpring } from 'react-native-reanimated';
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', 'simultaneous-handlers': originSimultaneousHandlers = [], 'wait-for': waitFor = [], style = {}, bindtouchstart, catchtouchstart, bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove, bindtouchend, catchtouchend } = 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 || []);
@@ -61,6 +149,8 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
61
149
  const yInertialMotion = useSharedValue(false);
62
150
  const isFirstTouch = useSharedValue(true);
63
151
  const touchEvent = useSharedValue('');
152
+ const initialViewPosition = useSharedValue({ x: x || 0, y: y || 0 });
153
+ const lastChangeTime = useSharedValue(0);
64
154
  const MovableAreaLayout = useContext(MovableAreaContext);
65
155
  const simultaneousHandlers = flatGesture(originSimultaneousHandlers);
66
156
  const waitForHandlers = flatGesture(waitFor);
@@ -104,25 +194,21 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
104
194
  const { x: newX, y: newY } = checkBoundaryPosition({ positionX: Number(x), positionY: Number(y) });
105
195
  if (direction === 'horizontal' || direction === 'all') {
106
196
  offsetX.value = animation
107
- ? withSpring(newX, {
108
- duration: 1500,
109
- dampingRatio: 0.8
110
- })
197
+ ? withWechatSpring(newX, damping)
111
198
  : newX;
112
199
  }
113
200
  if (direction === 'vertical' || direction === 'all') {
114
201
  offsetY.value = animation
115
- ? withSpring(newY, {
116
- duration: 1500,
117
- dampingRatio: 0.8
118
- })
202
+ ? withWechatSpring(newY, damping)
119
203
  : newY;
120
204
  }
121
- runOnJS(handleTriggerChange)({
122
- x: newX,
123
- y: newY,
124
- type: 'setData'
125
- });
205
+ if (bindchange) {
206
+ runOnJS(runOnJSCallback)('handleTriggerChange', {
207
+ x: newX,
208
+ y: newY,
209
+ type: 'setData'
210
+ });
211
+ }
126
212
  }
127
213
  })();
128
214
  }, [x, y]);
@@ -132,16 +218,6 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
132
218
  resetBoundaryAndCheck({ width, height });
133
219
  }
134
220
  }, [MovableAreaLayout.height, MovableAreaLayout.width]);
135
- useAnimatedReaction(() => ({
136
- offsetX: offsetX.value,
137
- offsetY: offsetY.value
138
- }), (currentValue) => {
139
- const { offsetX, offsetY } = currentValue;
140
- runOnJS(handleTriggerChange)({
141
- x: offsetX,
142
- y: offsetY
143
- });
144
- });
145
221
  const getTouchSource = useCallback((offsetX, offsetY) => {
146
222
  const hasOverBoundary = offsetX < draggableXRange.value[0] || offsetX > draggableXRange.value[1] ||
147
223
  offsetY < draggableYRange.value[0] || offsetY > draggableYRange.value[1];
@@ -230,14 +306,14 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
230
306
  setHeight(height || 0);
231
307
  }
232
308
  nodeRef.current?.measure((x, y, width, height) => {
233
- const { y: navigationY = 0 } = navigation?.layout || {};
309
+ const { top: navigationY = 0 } = navigation?.layout || {};
234
310
  layoutRef.current = { x, y: y - navigationY, width, height, offsetLeft: 0, offsetTop: 0 };
235
311
  resetBoundaryAndCheck({ width, height });
236
312
  });
237
313
  props.onLayout && props.onLayout(e);
238
314
  };
239
315
  const extendEvent = useCallback((e, type) => {
240
- const { y: navigationY = 0 } = navigation?.layout || {};
316
+ const { top: navigationY = 0 } = navigation?.layout || {};
241
317
  const touchArr = [e.changedTouches, e.allTouches];
242
318
  touchArr.forEach(touches => {
243
319
  touches && touches.forEach((item) => {
@@ -259,11 +335,13 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
259
335
  });
260
336
  }, []);
261
337
  const triggerStartOnJS = ({ e }) => {
338
+ const { bindtouchstart, catchtouchstart } = propsRef.current;
262
339
  extendEvent(e, 'start');
263
340
  bindtouchstart && bindtouchstart(e);
264
341
  catchtouchstart && catchtouchstart(e);
265
342
  };
266
343
  const triggerMoveOnJS = ({ e, hasTouchmove, hasCatchTouchmove, touchEvent }) => {
344
+ const { bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove } = propsRef.current;
267
345
  extendEvent(e, 'move');
268
346
  if (hasTouchmove) {
269
347
  if (touchEvent === 'htouchmove') {
@@ -285,17 +363,34 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
285
363
  }
286
364
  };
287
365
  const triggerEndOnJS = ({ e }) => {
366
+ const { bindtouchend, catchtouchend } = propsRef.current;
288
367
  extendEvent(e, 'end');
289
368
  bindtouchend && bindtouchend(e);
290
369
  catchtouchend && catchtouchend(e);
291
370
  };
371
+ const runOnJSCallbackRef = useRef({
372
+ handleTriggerChange,
373
+ triggerStartOnJS,
374
+ triggerMoveOnJS,
375
+ triggerEndOnJS
376
+ });
377
+ const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef);
378
+ // 节流版本的change事件触发
379
+ const handleTriggerChangeThrottled = useCallback(({ x, y, type }) => {
380
+ 'worklet';
381
+ const now = Date.now();
382
+ if (now - lastChangeTime.value >= changeThrottleTime) {
383
+ lastChangeTime.value = now;
384
+ runOnJS(runOnJSCallback)('handleTriggerChange', { x, y, type });
385
+ }
386
+ }, [changeThrottleTime]);
292
387
  const gesture = useMemo(() => {
293
388
  const handleTriggerMove = (e) => {
294
389
  'worklet';
295
390
  const hasTouchmove = !!bindhtouchmove || !!bindvtouchmove || !!bindtouchmove;
296
391
  const hasCatchTouchmove = !!catchhtouchmove || !!catchvtouchmove || !!catchtouchmove;
297
392
  if (hasTouchmove || hasCatchTouchmove) {
298
- runOnJS(triggerMoveOnJS)({
393
+ runOnJS(runOnJSCallback)('triggerMoveOnJS', {
299
394
  e,
300
395
  touchEvent: touchEvent.value,
301
396
  hasTouchmove,
@@ -313,8 +408,15 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
313
408
  y: changedTouches.y
314
409
  };
315
410
  if (bindtouchstart || catchtouchstart) {
316
- runOnJS(triggerStartOnJS)({ e });
411
+ runOnJS(runOnJSCallback)('triggerStartOnJS', { e });
317
412
  }
413
+ })
414
+ .onStart(() => {
415
+ 'worklet';
416
+ initialViewPosition.value = {
417
+ x: offsetX.value,
418
+ y: offsetY.value
419
+ };
318
420
  })
319
421
  .onTouchesMove((e) => {
320
422
  'worklet';
@@ -325,87 +427,111 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
325
427
  isFirstTouch.value = false;
326
428
  }
327
429
  handleTriggerMove(e);
430
+ })
431
+ .onUpdate((e) => {
432
+ 'worklet';
328
433
  if (disabled)
329
434
  return;
330
- const changeX = changedTouches.x - startPosition.value.x;
331
- const changeY = changedTouches.y - startPosition.value.y;
332
435
  if (direction === 'horizontal' || direction === 'all') {
333
- const newX = offsetX.value + changeX;
436
+ const newX = initialViewPosition.value.x + e.translationX;
334
437
  if (!outOfBounds) {
335
438
  const { x } = checkBoundaryPosition({ positionX: newX, positionY: offsetY.value });
336
439
  offsetX.value = x;
337
440
  }
338
441
  else {
339
- offsetX.value = newX;
442
+ offsetX.value = applyBoundaryDecline(newX, draggableXRange.value);
340
443
  }
341
444
  }
342
445
  if (direction === 'vertical' || direction === 'all') {
343
- const newY = offsetY.value + changeY;
446
+ const newY = initialViewPosition.value.y + e.translationY;
344
447
  if (!outOfBounds) {
345
448
  const { y } = checkBoundaryPosition({ positionX: offsetX.value, positionY: newY });
346
449
  offsetY.value = y;
347
450
  }
348
451
  else {
349
- offsetY.value = newY;
452
+ offsetY.value = applyBoundaryDecline(newY, draggableYRange.value);
350
453
  }
351
454
  }
455
+ if (bindchange) {
456
+ // 使用节流版本减少 runOnJS 调用
457
+ handleTriggerChangeThrottled({
458
+ x: offsetX.value,
459
+ y: offsetY.value
460
+ });
461
+ }
352
462
  })
353
463
  .onTouchesUp((e) => {
354
464
  'worklet';
355
465
  isFirstTouch.value = true;
356
466
  isMoving.value = false;
357
467
  if (bindtouchend || catchtouchend) {
358
- runOnJS(triggerEndOnJS)({ e });
468
+ runOnJS(runOnJSCallback)('triggerEndOnJS', { e });
359
469
  }
470
+ })
471
+ .onEnd((e) => {
472
+ 'worklet';
473
+ isMoving.value = false;
360
474
  if (disabled)
361
475
  return;
362
- if (!inertia) {
476
+ // 处理没有惯性且超出边界的回弹
477
+ if (!inertia && outOfBounds) {
363
478
  const { x, y } = checkBoundaryPosition({ positionX: offsetX.value, positionY: offsetY.value });
364
- if (x !== offsetX.value) {
365
- offsetX.value = animation
366
- ? withSpring(x, {
367
- duration: 1500,
368
- dampingRatio: 0.8
369
- })
370
- : x;
479
+ if (x !== offsetX.value || y !== offsetY.value) {
480
+ if (x !== offsetX.value) {
481
+ offsetX.value = animation
482
+ ? withWechatSpring(x, damping)
483
+ : x;
484
+ }
485
+ if (y !== offsetY.value) {
486
+ offsetY.value = animation
487
+ ? withWechatSpring(y, damping)
488
+ : y;
489
+ }
490
+ if (bindchange) {
491
+ runOnJS(runOnJSCallback)('handleTriggerChange', {
492
+ x,
493
+ y
494
+ });
495
+ }
371
496
  }
372
- if (y !== offsetY.value) {
373
- offsetY.value = animation
374
- ? withSpring(y, {
375
- duration: 1500,
376
- dampingRatio: 0.8
377
- })
378
- : y;
497
+ }
498
+ else if (inertia) {
499
+ // 惯性处理 - 使用微信小程序friction算法
500
+ if (direction === 'horizontal' || direction === 'all') {
501
+ xInertialMotion.value = true;
502
+ offsetX.value = withWechatDecay(e.velocityX / 10, offsetX.value, draggableXRange.value, friction, () => {
503
+ xInertialMotion.value = false;
504
+ if (bindchange) {
505
+ runOnJS(runOnJSCallback)('handleTriggerChange', {
506
+ x: offsetX.value,
507
+ y: offsetY.value
508
+ });
509
+ }
510
+ });
511
+ }
512
+ if (direction === 'vertical' || direction === 'all') {
513
+ yInertialMotion.value = true;
514
+ offsetY.value = withWechatDecay(e.velocityY / 10, offsetY.value, draggableYRange.value, friction, () => {
515
+ yInertialMotion.value = false;
516
+ if (bindchange) {
517
+ runOnJS(runOnJSCallback)('handleTriggerChange', {
518
+ x: offsetX.value,
519
+ y: offsetY.value
520
+ });
521
+ }
522
+ });
379
523
  }
380
524
  }
381
525
  })
382
- .onFinalize((e) => {
383
- 'worklet';
384
- isMoving.value = false;
385
- if (!inertia || disabled || !animation)
386
- return;
387
- if (direction === 'horizontal' || direction === 'all') {
388
- xInertialMotion.value = true;
389
- offsetX.value = withDecay({
390
- velocity: e.velocityX / 10,
391
- rubberBandEffect: outOfBounds,
392
- clamp: draggableXRange.value
393
- }, () => {
394
- xInertialMotion.value = false;
395
- });
526
+ .withRef(movableGestureRef);
527
+ if (!disableEventPassthrough) {
528
+ if (direction === 'horizontal') {
529
+ gesturePan.activeOffsetX([-5, 5]).failOffsetY([-5, 5]);
396
530
  }
397
- if (direction === 'vertical' || direction === 'all') {
398
- yInertialMotion.value = true;
399
- offsetY.value = withDecay({
400
- velocity: e.velocityY / 10,
401
- rubberBandEffect: outOfBounds,
402
- clamp: draggableYRange.value
403
- }, () => {
404
- yInertialMotion.value = false;
405
- });
531
+ else if (direction === 'vertical') {
532
+ gesturePan.activeOffsetY([-5, 5]).failOffsetX([-5, 5]);
406
533
  }
407
- })
408
- .withRef(movableGestureRef);
534
+ }
409
535
  if (simultaneousHandlers && simultaneousHandlers.length) {
410
536
  gesturePan.simultaneousWithExternalGesture(...simultaneousHandlers);
411
537
  }
@@ -452,7 +578,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
452
578
  'catchhtouchmove',
453
579
  'catchtouchend'
454
580
  ]);
455
- const innerProps = useInnerProps(filterProps, extendObject({
581
+ const innerProps = useInnerProps(extendObject({}, filterProps, {
456
582
  ref: nodeRef,
457
583
  onLayout: onLayout,
458
584
  style: [innerStyle, animatedStyles, layoutStyle]
@@ -1,5 +1,13 @@
1
+ /**
2
+ * ✔ hover-class
3
+ * ✘ hover-stop-propagation
4
+ * ✔ hover-start-time
5
+ * ✔ hover-stay-time
6
+ * ✔ open-type
7
+ * ✔ url
8
+ * ✔ delta
9
+ */
1
10
  import { useCallback, forwardRef, createElement } from 'react';
2
- import useInnerProps from './getInnerListeners';
3
11
  import { redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy';
4
12
  import MpxView from './mpx-view';
5
13
  const _Navigator = forwardRef((props, ref) => {
@@ -23,10 +31,10 @@ const _Navigator = forwardRef((props, ref) => {
23
31
  break;
24
32
  }
25
33
  }, [openType, url, delta]);
26
- const innerProps = useInnerProps(props, {
34
+ const innerProps = {
27
35
  ref,
28
36
  bindtap: handleClick
29
- });
37
+ };
30
38
  return createElement(MpxView, innerProps, children);
31
39
  });
32
40
  _Navigator.displayName = 'MpxNavigator';