@mpxjs/webpack-plugin 2.9.65 → 2.9.66
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/index.js +5 -1
- package/lib/platform/style/wx/index.js +17 -46
- package/lib/react/processTemplate.js +4 -2
- package/lib/runtime/components/react/context.ts +8 -0
- package/lib/runtime/components/react/dist/context.js +1 -0
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +10 -2
- package/lib/runtime/components/react/dist/mpx-swiper/carouse.jsx +76 -76
- package/lib/runtime/components/react/dist/mpx-view.jsx +30 -11
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +1 -1
- package/lib/runtime/components/react/dist/useAnimationHooks.js +215 -0
- package/lib/runtime/components/react/dist/utils.jsx +2 -1
- package/lib/runtime/components/react/mpx-scroll-view.tsx +11 -1
- package/lib/runtime/components/react/mpx-swiper/carouse.tsx +75 -74
- package/lib/runtime/components/react/mpx-view.tsx +39 -19
- package/lib/runtime/components/react/mpx-web-view.tsx +1 -1
- package/lib/runtime/components/react/types/common.ts +8 -2
- package/lib/runtime/components/react/useAnimationHooks.ts +248 -0
- package/lib/runtime/components/react/utils.tsx +6 -4
- package/lib/template-compiler/compiler.js +22 -17
- package/package.json +4 -3
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { Easing, useSharedValue, withTiming, useAnimatedStyle, withSequence, withDelay, makeMutable, cancelAnimation } from 'react-native-reanimated';
|
|
3
|
+
// 微信 timingFunction 和 RN Easing 对应关系
|
|
4
|
+
const EasingKey = {
|
|
5
|
+
linear: Easing.linear,
|
|
6
|
+
ease: Easing.ease,
|
|
7
|
+
'ease-in': Easing.in(Easing.ease),
|
|
8
|
+
'ease-in-out': Easing.inOut(Easing.ease),
|
|
9
|
+
'ease-out': Easing.out(Easing.ease)
|
|
10
|
+
// 'step-start': '',
|
|
11
|
+
// 'step-end': ''
|
|
12
|
+
};
|
|
13
|
+
const TransformInitial = {
|
|
14
|
+
// matrix: 0,
|
|
15
|
+
// matrix3d: 0,
|
|
16
|
+
rotate: '0deg',
|
|
17
|
+
rotateX: '0deg',
|
|
18
|
+
rotateY: '0deg',
|
|
19
|
+
rotateZ: '0deg',
|
|
20
|
+
// rotate3d:[0,0,0]
|
|
21
|
+
scale: 1,
|
|
22
|
+
// scale3d: [1, 1, 1],
|
|
23
|
+
scaleX: 1,
|
|
24
|
+
scaleY: 1,
|
|
25
|
+
// scaleZ: 1,
|
|
26
|
+
skew: 0,
|
|
27
|
+
skewX: '0deg',
|
|
28
|
+
skewY: '0deg',
|
|
29
|
+
translate: 0,
|
|
30
|
+
// translate3d: 0,
|
|
31
|
+
translateX: 0,
|
|
32
|
+
translateY: 0
|
|
33
|
+
// translateZ: 0,
|
|
34
|
+
};
|
|
35
|
+
// 动画默认初始值
|
|
36
|
+
const InitialValue = Object.assign({
|
|
37
|
+
opacity: 1,
|
|
38
|
+
backgroundColor: 'transparent',
|
|
39
|
+
width: 0,
|
|
40
|
+
height: 0,
|
|
41
|
+
top: 0,
|
|
42
|
+
right: 0,
|
|
43
|
+
bottom: 0,
|
|
44
|
+
left: 0,
|
|
45
|
+
transformOrigin: ['50%', '50%', 0]
|
|
46
|
+
}, TransformInitial);
|
|
47
|
+
const TransformOrigin = 'transformOrigin';
|
|
48
|
+
// deg 角度
|
|
49
|
+
// const isDeg = (key: RuleKey) => ['rotateX', 'rotateY', 'rotateZ', 'rotate', 'skewX', 'skewY'].includes(key)
|
|
50
|
+
// 背景色
|
|
51
|
+
// const isBg = (key: RuleKey) => key === 'backgroundColor'
|
|
52
|
+
// transform
|
|
53
|
+
const isTransform = (key) => Object.keys(TransformInitial).includes(key);
|
|
54
|
+
export default function useAnimationHooks(props) {
|
|
55
|
+
const { style: originalStyle = {}, animation } = props;
|
|
56
|
+
// id 标识
|
|
57
|
+
const id = animation?.id || -1;
|
|
58
|
+
// 有动画样式的 style key
|
|
59
|
+
const animatedStyleKeys = useSharedValue([]);
|
|
60
|
+
const animatedKeys = useRef({});
|
|
61
|
+
// ** 全量 style prop sharedValue
|
|
62
|
+
// 不能做增量的原因:
|
|
63
|
+
// 1 尝试用 useRef,但 useAnimatedStyle 访问后的 ref 不能在增加新的值,被冻结
|
|
64
|
+
// 2 尝试用 useSharedValue,因为实际触发的 style prop 需要是 sharedValue 才能驱动动画,若外层 shareValMap 也是 sharedValue,动画无法驱动。
|
|
65
|
+
const shareValMap = useMemo(() => {
|
|
66
|
+
return Object.keys(InitialValue).reduce((valMap, key) => {
|
|
67
|
+
const defaultVal = getInitialVal(key, isTransform(key));
|
|
68
|
+
valMap[key] = makeMutable(defaultVal);
|
|
69
|
+
return valMap;
|
|
70
|
+
}, {});
|
|
71
|
+
}, []);
|
|
72
|
+
// ** 获取动画样式prop & 驱动动画
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (id === -1)
|
|
75
|
+
return;
|
|
76
|
+
// 更新动画样式 key map
|
|
77
|
+
animatedKeys.current = getAnimatedStyleKeys();
|
|
78
|
+
const keys = Object.keys(animatedKeys.current);
|
|
79
|
+
animatedStyleKeys.value = formatAnimatedKeys([TransformOrigin, ...keys]);
|
|
80
|
+
// 驱动动画
|
|
81
|
+
createAnimation(keys);
|
|
82
|
+
}, [id]);
|
|
83
|
+
// ** 清空动画
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
return () => {
|
|
86
|
+
Object.values(shareValMap).forEach((value) => {
|
|
87
|
+
cancelAnimation(value);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
}, []);
|
|
91
|
+
// 根据 animation action 创建&驱动动画 key => wi
|
|
92
|
+
function createAnimation(animatedKeys = []) {
|
|
93
|
+
const actions = animation?.actions || [];
|
|
94
|
+
const sequence = {};
|
|
95
|
+
const lastValueMap = {};
|
|
96
|
+
actions.forEach(({ animatedOption, rules, transform }, index) => {
|
|
97
|
+
const { delay, duration, timingFunction, transformOrigin } = animatedOption;
|
|
98
|
+
const easing = EasingKey[timingFunction] || Easing.inOut(Easing.quad);
|
|
99
|
+
let needSetCallback = true;
|
|
100
|
+
const setTransformOrigin = (finished) => {
|
|
101
|
+
'worklet';
|
|
102
|
+
// 动画结束后设置下一次transformOrigin
|
|
103
|
+
if (finished) {
|
|
104
|
+
if (index < actions.length - 1) {
|
|
105
|
+
const transformOrigin = actions[index + 1].animatedOption?.transformOrigin;
|
|
106
|
+
transformOrigin && (shareValMap[TransformOrigin].value = transformOrigin);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
if (index === 0) {
|
|
111
|
+
// 设置当次中心
|
|
112
|
+
shareValMap[TransformOrigin].value = transformOrigin;
|
|
113
|
+
}
|
|
114
|
+
// 添加每个key的多次step动画
|
|
115
|
+
animatedKeys.forEach(key => {
|
|
116
|
+
let toVal = (rules.get(key) || transform.get(key));
|
|
117
|
+
// key不存在,第一轮取shareValMap[key]value,非第一轮取上一轮的
|
|
118
|
+
if (!toVal) {
|
|
119
|
+
toVal = index > 0 ? lastValueMap[key] : shareValMap[key].value;
|
|
120
|
+
}
|
|
121
|
+
const animation = getAnimation({ key, value: toVal }, { delay, duration, easing }, needSetCallback ? setTransformOrigin : undefined);
|
|
122
|
+
needSetCallback = false;
|
|
123
|
+
if (!sequence[key]) {
|
|
124
|
+
sequence[key] = [animation];
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
sequence[key].push(animation);
|
|
128
|
+
}
|
|
129
|
+
// 更新一下 lastValueMap
|
|
130
|
+
lastValueMap[key] = toVal;
|
|
131
|
+
});
|
|
132
|
+
// 赋值驱动动画
|
|
133
|
+
animatedKeys.forEach((key) => {
|
|
134
|
+
const animations = sequence[key];
|
|
135
|
+
shareValMap[key].value = withSequence(...animations);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// 创建单个animation
|
|
140
|
+
function getAnimation({ key, value }, { delay, duration, easing }, callback) {
|
|
141
|
+
const animation = typeof callback === 'function'
|
|
142
|
+
? withTiming(value, { duration, easing }, callback)
|
|
143
|
+
: withTiming(value, { duration, easing });
|
|
144
|
+
return delay ? withDelay(delay, animation) : animation;
|
|
145
|
+
}
|
|
146
|
+
// 获取初始值(prop style or 默认值)
|
|
147
|
+
function getInitialVal(key, isTransform = false) {
|
|
148
|
+
if (isTransform && originalStyle.transform?.length) {
|
|
149
|
+
let initialVal = InitialValue[key];
|
|
150
|
+
// 仅支持 { transform: [{rotateX: '45deg'}, {rotateZ: '0.785398rad'}] } 格式的初始样式
|
|
151
|
+
originalStyle.transform.forEach(item => {
|
|
152
|
+
if (item[key] !== undefined)
|
|
153
|
+
initialVal = item[key];
|
|
154
|
+
});
|
|
155
|
+
return initialVal;
|
|
156
|
+
}
|
|
157
|
+
return originalStyle[key] === undefined ? InitialValue[key] : originalStyle[key];
|
|
158
|
+
}
|
|
159
|
+
// 循环 animation actions 获取所有有动画的 style prop name
|
|
160
|
+
function getAnimatedStyleKeys() {
|
|
161
|
+
return (animation?.actions || []).reduce((keyMap, action) => {
|
|
162
|
+
const { rules, transform } = action;
|
|
163
|
+
const ruleArr = [...rules.keys(), ...transform.keys()];
|
|
164
|
+
ruleArr.forEach(key => {
|
|
165
|
+
if (!keyMap[key])
|
|
166
|
+
keyMap[key] = true;
|
|
167
|
+
});
|
|
168
|
+
return keyMap;
|
|
169
|
+
}, animatedKeys.current);
|
|
170
|
+
}
|
|
171
|
+
// animated key transform 格式化
|
|
172
|
+
function formatAnimatedKeys(keys = []) {
|
|
173
|
+
const animatedKeys = [];
|
|
174
|
+
const transforms = [];
|
|
175
|
+
keys.forEach(key => {
|
|
176
|
+
if (isTransform(key)) {
|
|
177
|
+
transforms.push(key);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
animatedKeys.push(key);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
if (transforms.length)
|
|
184
|
+
animatedKeys.push(transforms);
|
|
185
|
+
return animatedKeys;
|
|
186
|
+
}
|
|
187
|
+
// transform 数组转对象
|
|
188
|
+
function getTransformObj() {
|
|
189
|
+
'worklet';
|
|
190
|
+
const transforms = originalStyle.transform || [];
|
|
191
|
+
return transforms.reduce((transformObj, item) => {
|
|
192
|
+
return Object.assign(transformObj, item);
|
|
193
|
+
}, {});
|
|
194
|
+
}
|
|
195
|
+
// ** 生成动画样式
|
|
196
|
+
return useAnimatedStyle(() => {
|
|
197
|
+
// console.info(`useAnimatedStyle styles=`, originalStyle)
|
|
198
|
+
return animatedStyleKeys.value.reduce((styles, key) => {
|
|
199
|
+
// console.info('getAnimationStyles', key, shareValMap[key].value)
|
|
200
|
+
if (Array.isArray(key)) {
|
|
201
|
+
const transformStyle = getTransformObj();
|
|
202
|
+
key.forEach((transformKey) => {
|
|
203
|
+
transformStyle[transformKey] = shareValMap[transformKey].value;
|
|
204
|
+
});
|
|
205
|
+
styles.transform = Object.entries(transformStyle).map(([key, value]) => {
|
|
206
|
+
return { [key]: value };
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
styles[key] = shareValMap[key].value;
|
|
211
|
+
}
|
|
212
|
+
return styles;
|
|
213
|
+
}, Object.assign({}, originalStyle));
|
|
214
|
+
});
|
|
215
|
+
}
|
|
@@ -87,7 +87,8 @@ export const getRestProps = (transferProps = {}, originProps = {}, deletePropsKe
|
|
|
87
87
|
export function isText(ele) {
|
|
88
88
|
if (isValidElement(ele)) {
|
|
89
89
|
const displayName = ele.type?.displayName;
|
|
90
|
-
|
|
90
|
+
const isCustomText = ele.type?.isCustomText;
|
|
91
|
+
return displayName === 'mpx-text' || displayName === 'Text' || !!isCustomText;
|
|
91
92
|
}
|
|
92
93
|
return false;
|
|
93
94
|
}
|
|
@@ -33,12 +33,13 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import { ScrollView } from 'react-native-gesture-handler'
|
|
35
35
|
import { View, RefreshControl, NativeSyntheticEvent, NativeScrollEvent, LayoutChangeEvent, ViewStyle } from 'react-native'
|
|
36
|
-
import { JSX, ReactNode, RefObject, useRef, useState, useEffect, forwardRef } from 'react'
|
|
36
|
+
import { JSX, ReactNode, RefObject, useRef, useState, useEffect, forwardRef, useContext } from 'react'
|
|
37
37
|
import { useAnimatedRef } from 'react-native-reanimated'
|
|
38
38
|
import { warn } from '@mpxjs/utils'
|
|
39
39
|
import useInnerProps, { getCustomEvent } from './getInnerListeners'
|
|
40
40
|
import useNodesRef, { HandlerRef } from './useNodesRef'
|
|
41
41
|
import { splitProps, splitStyle, useTransformStyle, useLayout, wrapChildren } from './utils'
|
|
42
|
+
import { IntersectionObserverContext } from './context'
|
|
42
43
|
|
|
43
44
|
interface ScrollViewProps {
|
|
44
45
|
children?: ReactNode;
|
|
@@ -60,6 +61,7 @@ interface ScrollViewProps {
|
|
|
60
61
|
'scroll-top'?: number;
|
|
61
62
|
'scroll-left'?: number;
|
|
62
63
|
'enable-offset'?: boolean;
|
|
64
|
+
'enable-trigger-intersection-observer'?: boolean;
|
|
63
65
|
'enable-var'?: boolean;
|
|
64
66
|
'external-var-context'?: Record<string, any>;
|
|
65
67
|
'parent-font-size'?: number;
|
|
@@ -107,6 +109,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
107
109
|
'scroll-x': scrollX = false,
|
|
108
110
|
'scroll-y': scrollY = false,
|
|
109
111
|
'enable-back-to-top': enableBackToTop = false,
|
|
112
|
+
'enable-trigger-intersection-observer': enableTriggerIntersectionObserver = false,
|
|
110
113
|
'paging-enabled': pagingEnabled = false,
|
|
111
114
|
'upper-threshold': upperThreshold = 50,
|
|
112
115
|
'lower-threshold': lowerThreshold = 50,
|
|
@@ -139,6 +142,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
139
142
|
const hasCallScrollToUpper = useRef(true)
|
|
140
143
|
const hasCallScrollToLower = useRef(false)
|
|
141
144
|
const initialTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
145
|
+
const intersectionObservers = useContext(IntersectionObserverContext)
|
|
142
146
|
|
|
143
147
|
const {
|
|
144
148
|
normalStyle,
|
|
@@ -285,6 +289,11 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
285
289
|
}, props)
|
|
286
290
|
)
|
|
287
291
|
updateScrollOptions(e, { scrollLeft, scrollTop })
|
|
292
|
+
if (enableTriggerIntersectionObserver && intersectionObservers) {
|
|
293
|
+
for (const key in intersectionObservers) {
|
|
294
|
+
intersectionObservers[key].throttleMeasure()
|
|
295
|
+
}
|
|
296
|
+
}
|
|
288
297
|
}
|
|
289
298
|
|
|
290
299
|
function onScrollEnd (e: NativeSyntheticEvent<NativeScrollEvent>) {
|
|
@@ -407,6 +416,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
407
416
|
'scroll-x',
|
|
408
417
|
'scroll-y',
|
|
409
418
|
'enable-back-to-top',
|
|
419
|
+
'enable-trigger-intersection-observer',
|
|
410
420
|
'paging-enabled',
|
|
411
421
|
'show-scrollbar',
|
|
412
422
|
'upper-threshold',
|
|
@@ -87,6 +87,8 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
87
87
|
const initOffsetIndex = initIndex + (props.circular && totalElements > 1 ? 1 : 0)
|
|
88
88
|
const defaultX = (defaultWidth * initOffsetIndex) || 0
|
|
89
89
|
const defaultY = (defaultHeight * initOffsetIndex) || 0
|
|
90
|
+
// 主动scorllTo时是否要出发onScrollEnd
|
|
91
|
+
const needTriggerScrollEnd = useRef(true)
|
|
90
92
|
// 内部存储上一次的offset值
|
|
91
93
|
const autoplayTimerRef = useRef<ReturnType <typeof setTimeout> | null>(null)
|
|
92
94
|
const scrollViewRef = useRef<ScrollView & View>(null)
|
|
@@ -100,22 +102,21 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
100
102
|
// 内部存储上一次的偏移量
|
|
101
103
|
const internalsRef = useRef({
|
|
102
104
|
offset: {
|
|
103
|
-
x:
|
|
104
|
-
y:
|
|
105
|
+
x: 0,
|
|
106
|
+
y: 0
|
|
105
107
|
},
|
|
106
108
|
isScrolling: false
|
|
107
109
|
})
|
|
108
110
|
const isDragRef = useRef(false)
|
|
109
111
|
const [state, setState] = useState({
|
|
110
|
-
children: newChild,
|
|
111
112
|
width: dir === 'x' && typeof defaultWidth === 'number' ? defaultWidth - previousMargin - nextMargin : defaultWidth,
|
|
112
113
|
height: dir === 'y' && typeof defaultHeight === 'number' ? defaultHeight - previousMargin - nextMargin : defaultHeight,
|
|
113
114
|
// 真正的游标索引, 从0开始
|
|
114
115
|
index: initIndex,
|
|
115
116
|
total: totalElements,
|
|
116
117
|
offset: {
|
|
117
|
-
x:
|
|
118
|
-
y:
|
|
118
|
+
x: 0,
|
|
119
|
+
y: 0
|
|
119
120
|
},
|
|
120
121
|
dir
|
|
121
122
|
} as CarouseState)
|
|
@@ -138,25 +139,38 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
138
139
|
|
|
139
140
|
useEffect(() => {
|
|
140
141
|
// 确认这个是变化的props变化的时候才执行,还是初始化的时候就执行
|
|
141
|
-
if (!props.autoplay && props.current !== state.index) {
|
|
142
|
+
if (!props.autoplay && props.current !== undefined && props.current !== state.index) {
|
|
142
143
|
const initIndex = props.current || 0
|
|
143
144
|
// 这里要排除超过元素个数的设置
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
const { nextIndex, nextOffset } = getMultiNextConfig(props.current)
|
|
146
|
+
// 1. 安卓需要主动更新下内部状态, 2. IOS不能触发完wcrollTo之后立即updateState, 会造成滑动两次
|
|
147
|
+
// 2. setTimeout 是fix 当再渲染过程中触发scrollTo失败的问题
|
|
148
|
+
if (Platform.OS === 'ios') {
|
|
149
|
+
needTriggerScrollEnd.current = false
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
scrollViewRef.current?.scrollTo({
|
|
152
|
+
...nextOffset,
|
|
153
|
+
animated: true
|
|
154
|
+
})
|
|
155
|
+
}, 50)
|
|
156
|
+
} else {
|
|
157
|
+
updateState(nextIndex, nextOffset)
|
|
149
158
|
}
|
|
150
|
-
state.offset = offset
|
|
151
|
-
internalsRef.current.offset = offset
|
|
152
|
-
setState((preState) => {
|
|
153
|
-
return {
|
|
154
|
-
...preState,
|
|
155
|
-
offset
|
|
156
|
-
}
|
|
157
|
-
})
|
|
158
159
|
}
|
|
159
|
-
}, [props.current])
|
|
160
|
+
}, [props.current, state.width, state.height])
|
|
161
|
+
|
|
162
|
+
function getMultiNextConfig (target: number) {
|
|
163
|
+
const step = state.dir === 'x' ? state.width : state.height
|
|
164
|
+
const targetPos = step * props.current
|
|
165
|
+
const targetOffset = {
|
|
166
|
+
x: dir === 'x' ? targetPos : 0,
|
|
167
|
+
y: dir === 'y' ? targetPos : 0
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
nextIndex: target,
|
|
171
|
+
nextOffset: targetOffset
|
|
172
|
+
}
|
|
173
|
+
}
|
|
160
174
|
/**
|
|
161
175
|
* @desc: 更新状态: index和offset, 并响应索引变化的事件
|
|
162
176
|
* scrollViewOffset: 移动到的目标位置
|
|
@@ -208,7 +222,6 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
208
222
|
nextIndex = isBack ? nextIndex - 2 : nextIndex
|
|
209
223
|
}
|
|
210
224
|
if (!props.circular) {
|
|
211
|
-
// nextIndex = isBack ? nextIndex - 2 : nextIndex
|
|
212
225
|
nextOffset = Object.assign({}, currentOffset, { [state.dir]: step * nextIndex })
|
|
213
226
|
} else {
|
|
214
227
|
if (isBack) {
|
|
@@ -254,13 +267,12 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
254
267
|
createAutoPlay()
|
|
255
268
|
return
|
|
256
269
|
}
|
|
257
|
-
if (!Array.isArray(
|
|
270
|
+
if (!Array.isArray(props.children)) {
|
|
258
271
|
return
|
|
259
272
|
}
|
|
260
273
|
const step = state.dir === 'x' ? state.width : state.height
|
|
261
274
|
const { nextOffset, autoMoveOffset, isAutoEnd } = getNextConfig(state.offset)
|
|
262
275
|
// 这里可以scroll到下一个元素, 但是把scrollView的偏移量在设置为content,视觉效果就没了吧
|
|
263
|
-
// scrollViewRef.current?.scrollTo({ x: nextOffset['x'], y: nextOffset['y'], animated: true })
|
|
264
276
|
if (Platform.OS === 'ios') {
|
|
265
277
|
if (!isAutoEnd) {
|
|
266
278
|
scrollViewRef.current?.scrollTo({ x: nextOffset.x, y: nextOffset.y, animated: true })
|
|
@@ -286,7 +298,6 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
286
298
|
// 安卓无法实现视觉的无缝连接, 只能回到真正的位置, 且安卓调用scrollTo不能触发onMomentumScrollEnd,还未找到为啥
|
|
287
299
|
if (state.dir === 'x') {
|
|
288
300
|
scrollViewRef.current?.scrollTo({ x: step, y: step, animated: true })
|
|
289
|
-
// scrollViewRef.current?.scrollTo({ x: autoMoveOffset.x, y: autoMoveOffset.y, animated: true })
|
|
290
301
|
} else {
|
|
291
302
|
scrollViewRef.current?.scrollTo({ x: autoMoveOffset.x, y: step, animated: true })
|
|
292
303
|
}
|
|
@@ -304,9 +315,15 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
304
315
|
|
|
305
316
|
/**
|
|
306
317
|
* 当用户开始拖动结束
|
|
318
|
+
* 注意: 当手动调用scrollTo的时候, 安卓不会触发onMomentumScrollEnd, IOS会触发onMomentumScrollEnd
|
|
307
319
|
*/
|
|
308
320
|
function onScrollEnd (event: NativeSyntheticEvent<NativeScrollEvent>) {
|
|
309
|
-
|
|
321
|
+
if (Platform.OS === 'ios' && !needTriggerScrollEnd.current) {
|
|
322
|
+
const { nextIndex, nextOffset } = getMultiNextConfig(props.current)
|
|
323
|
+
updateState(nextIndex, nextOffset)
|
|
324
|
+
needTriggerScrollEnd.current = true
|
|
325
|
+
return
|
|
326
|
+
}
|
|
310
327
|
if (totalElements === 1) {
|
|
311
328
|
return
|
|
312
329
|
}
|
|
@@ -334,57 +351,41 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
334
351
|
* @desc: 水平方向时,获取元素的布局,更新, 其中如果传递100%时需要依赖measure计算元算的宽高
|
|
335
352
|
*/
|
|
336
353
|
function onWrapperLayout (e: LayoutChangeEvent) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const isWDiff = state.width !== width
|
|
346
|
-
const isHDiff = state.height !== height
|
|
347
|
-
if (isWDiff || isHDiff) {
|
|
348
|
-
const changeState = {
|
|
349
|
-
width: isWDiff ? width : state.width,
|
|
350
|
-
height: isHDiff ? height : state.height
|
|
351
|
-
}
|
|
352
|
-
const attr = state.dir === 'x' ? 'width' : 'height'
|
|
353
|
-
changeState[attr] = changeState[attr] - previousMargin - nextMargin
|
|
354
|
-
const correctOffset = Object.assign({}, state.offset, {
|
|
355
|
-
[state.dir]: initOffsetIndex * (state.dir === 'x' ? changeState.width : changeState.height)
|
|
356
|
-
})
|
|
357
|
-
state.offset = correctOffset
|
|
358
|
-
state.width = changeState.width
|
|
359
|
-
state.height = changeState.height
|
|
360
|
-
setState((preState) => {
|
|
361
|
-
return {
|
|
362
|
-
...preState,
|
|
363
|
-
offset: correctOffset,
|
|
364
|
-
width: changeState.width,
|
|
365
|
-
height: changeState.height
|
|
366
|
-
}
|
|
367
|
-
})
|
|
368
|
-
scrollViewRef.current?.scrollTo({ x: correctOffset.x, y: correctOffset.y, animated: false })
|
|
354
|
+
scrollViewRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => {
|
|
355
|
+
layoutRef.current = { x, y, width, height, offsetLeft, offsetTop }
|
|
356
|
+
const isWDiff = state.width !== width
|
|
357
|
+
const isHDiff = state.height !== height
|
|
358
|
+
if (isWDiff || isHDiff) {
|
|
359
|
+
const changeState = {
|
|
360
|
+
width: isWDiff ? width : state.width,
|
|
361
|
+
height: isHDiff ? height : state.height
|
|
369
362
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
363
|
+
const attr = state.dir === 'x' ? 'width' : 'height'
|
|
364
|
+
changeState[attr] = changeState[attr] - previousMargin - nextMargin
|
|
365
|
+
const correctOffset = Object.assign({}, state.offset, {
|
|
366
|
+
[state.dir]: initOffsetIndex * (state.dir === 'x' ? changeState.width : changeState.height)
|
|
367
|
+
})
|
|
368
|
+
state.width = changeState.width
|
|
369
|
+
state.height = changeState.height
|
|
370
|
+
// 这里setState之后,会再触发重新渲染, renderScrollView会再次触发onScrollEnd,
|
|
371
|
+
setState((preState) => {
|
|
372
|
+
return {
|
|
373
|
+
...preState,
|
|
374
|
+
width: changeState.width,
|
|
375
|
+
height: changeState.height
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
props.getInnerLayout && props.getInnerLayout(layoutRef)
|
|
380
|
+
})
|
|
373
381
|
}
|
|
374
382
|
|
|
375
383
|
function getOffset (): Array<number> {
|
|
376
384
|
const step = state.dir === 'x' ? state.width : state.height
|
|
377
385
|
if (!step || Number.isNaN(+step)) return []
|
|
378
386
|
const offsetArray = []
|
|
379
|
-
|
|
380
|
-
offsetArray.push(
|
|
381
|
-
for (let i = 1; i < totalElements; i++) {
|
|
382
|
-
offsetArray.push(i * step - previousMargin)
|
|
383
|
-
}
|
|
384
|
-
} else {
|
|
385
|
-
for (let i = 0; i < totalElements; i++) {
|
|
386
|
-
offsetArray.push(i * step)
|
|
387
|
-
}
|
|
387
|
+
for (let i = 0; i < totalElements; i++) {
|
|
388
|
+
offsetArray.push(i * step)
|
|
388
389
|
}
|
|
389
390
|
return offsetArray
|
|
390
391
|
}
|
|
@@ -394,7 +395,7 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
394
395
|
const scrollElementProps = {
|
|
395
396
|
ref: scrollViewRef,
|
|
396
397
|
horizontal: props.horizontal,
|
|
397
|
-
pagingEnabled:
|
|
398
|
+
pagingEnabled: true,
|
|
398
399
|
snapToOffsets: offsetsArray,
|
|
399
400
|
decelerationRate: 0.99, // 'fast'
|
|
400
401
|
showsHorizontalScrollIndicator: false,
|
|
@@ -461,20 +462,21 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
461
462
|
}
|
|
462
463
|
|
|
463
464
|
function renderPages () {
|
|
464
|
-
const { width, height
|
|
465
|
+
const { width, height } = state
|
|
466
|
+
const { children } = props
|
|
465
467
|
const { circular } = props
|
|
466
468
|
const pageStyle = { width: width, height: height }
|
|
467
469
|
// 设置了previousMargin或者nextMargin,
|
|
468
470
|
// 1. 元素的宽度是减去这两个数目之和
|
|
469
471
|
// 2. previousMargin设置marginLeft正值, nextmargin设置marginRight负值
|
|
470
472
|
// 3. 第一个元素设置previousMargin 和 nextMargin, 最后一个元素
|
|
471
|
-
if (
|
|
473
|
+
if (totalElements > 1 && Array.isArray(children)) {
|
|
472
474
|
let arrElements: (Array<ReactNode>) = []
|
|
473
475
|
// pages = ["2", "0", "1", "2", "0"]
|
|
474
476
|
const pages = Array.isArray(children) ? Object.keys(children) : []
|
|
475
477
|
/* 无限循环的时候 */
|
|
476
478
|
if (circular) {
|
|
477
|
-
pages.unshift(
|
|
479
|
+
pages.unshift(totalElements - 1 + '')
|
|
478
480
|
pages.push('0')
|
|
479
481
|
}
|
|
480
482
|
arrElements = pages.map((page, i) => {
|
|
@@ -486,7 +488,6 @@ const _Carouse = forwardRef<HandlerRef<ScrollView & View, CarouseProps>, Carouse
|
|
|
486
488
|
} else if (i === pages.length - 1 && typeof width === 'number') {
|
|
487
489
|
nextMargin && (extraStyle.marginRight = nextMargin)
|
|
488
490
|
}
|
|
489
|
-
// return (<View style={[pageStyle, styles.slide, extraStyle]} key={ 'page' + i}>{children[+page]}</View>)
|
|
490
491
|
return (<View style={[pageStyle, styles.slide, extraStyle]} key={ 'page' + i}>
|
|
491
492
|
{wrapChildren(
|
|
492
493
|
{
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
import { View, TextStyle, NativeSyntheticEvent, ViewProps, ImageStyle, ImageResizeMode, StyleSheet, Image, LayoutChangeEvent, Text } from 'react-native'
|
|
8
8
|
import { useRef, useState, useEffect, forwardRef, ReactNode, JSX, Children, cloneElement } from 'react'
|
|
9
9
|
import useInnerProps from './getInnerListeners'
|
|
10
|
+
import Animated from 'react-native-reanimated'
|
|
11
|
+
import useAnimationHooks from './useAnimationHooks'
|
|
12
|
+
import type { AnimationProp } from './useAnimationHooks'
|
|
10
13
|
import { ExtendedViewStyle } from './types/common'
|
|
11
14
|
import useNodesRef, { HandlerRef } from './useNodesRef'
|
|
12
15
|
import { parseUrl, PERCENT_REGEX, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout } from './utils'
|
|
@@ -14,6 +17,7 @@ import LinearGradient from 'react-native-linear-gradient'
|
|
|
14
17
|
|
|
15
18
|
export interface _ViewProps extends ViewProps {
|
|
16
19
|
style?: ExtendedViewStyle
|
|
20
|
+
animation?: AnimationProp
|
|
17
21
|
children?: ReactNode | ReactNode[]
|
|
18
22
|
'hover-style'?: ExtendedViewStyle
|
|
19
23
|
'hover-start-time'?: number
|
|
@@ -24,6 +28,7 @@ export interface _ViewProps extends ViewProps {
|
|
|
24
28
|
'parent-font-size'?: number
|
|
25
29
|
'parent-width'?: number
|
|
26
30
|
'parent-height'?: number
|
|
31
|
+
'enable-animation'?: boolean
|
|
27
32
|
bindtouchstart?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
|
|
28
33
|
bindtouchmove?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
|
|
29
34
|
bindtouchend?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
|
|
@@ -650,9 +655,11 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
|
|
|
650
655
|
'enable-var': enableVar,
|
|
651
656
|
'external-var-context': externalVarContext,
|
|
652
657
|
'enable-background': enableBackground,
|
|
658
|
+
'enable-animation': enableAnimation,
|
|
653
659
|
'parent-font-size': parentFontSize,
|
|
654
660
|
'parent-width': parentWidth,
|
|
655
|
-
'parent-height': parentHeight
|
|
661
|
+
'parent-height': parentHeight,
|
|
662
|
+
animation
|
|
656
663
|
} = props
|
|
657
664
|
|
|
658
665
|
const [isHover, setIsHover] = useState(false)
|
|
@@ -747,9 +754,10 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
|
|
|
747
754
|
layoutProps
|
|
748
755
|
} = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef })
|
|
749
756
|
|
|
757
|
+
const viewStyle = Object.assign({}, innerStyle, layoutStyle)
|
|
750
758
|
const innerProps = useInnerProps(props, {
|
|
751
759
|
ref: nodeRef,
|
|
752
|
-
style:
|
|
760
|
+
style: viewStyle,
|
|
753
761
|
...layoutProps,
|
|
754
762
|
...(hoverStyle && {
|
|
755
763
|
bindtouchstart: onTouchStart,
|
|
@@ -764,25 +772,37 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
|
|
|
764
772
|
layoutRef
|
|
765
773
|
})
|
|
766
774
|
|
|
767
|
-
|
|
768
|
-
|
|
775
|
+
enableAnimation = enableAnimation || !!animation
|
|
776
|
+
const enableAnimationRef = useRef(enableAnimation)
|
|
777
|
+
if (enableAnimationRef.current !== enableAnimation) {
|
|
778
|
+
throw new Error('[Mpx runtime error]: animation use should be stable in the component lifecycle, or you can set [enable-animation] with true.')
|
|
779
|
+
}
|
|
780
|
+
const finalStyle = enableAnimation
|
|
781
|
+
? useAnimationHooks({
|
|
782
|
+
animation,
|
|
783
|
+
style: viewStyle
|
|
784
|
+
})
|
|
785
|
+
: viewStyle
|
|
786
|
+
const childNode = wrapWithChildren(props, {
|
|
787
|
+
hasVarDec,
|
|
788
|
+
enableBackground: enableBackgroundRef.current,
|
|
789
|
+
textStyle,
|
|
790
|
+
backgroundStyle,
|
|
791
|
+
varContext: varContextRef.current,
|
|
792
|
+
textProps
|
|
793
|
+
})
|
|
794
|
+
return animation?.actions?.length
|
|
795
|
+
? (<Animated.View
|
|
769
796
|
{...innerProps}
|
|
797
|
+
style={finalStyle}
|
|
770
798
|
>
|
|
771
|
-
{
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
backgroundStyle,
|
|
779
|
-
varContext: varContextRef.current,
|
|
780
|
-
textProps
|
|
781
|
-
}
|
|
782
|
-
)
|
|
783
|
-
}
|
|
784
|
-
</View>
|
|
785
|
-
)
|
|
799
|
+
{childNode}
|
|
800
|
+
</Animated.View>)
|
|
801
|
+
: (<View
|
|
802
|
+
{...innerProps}
|
|
803
|
+
>
|
|
804
|
+
{childNode}
|
|
805
|
+
</View>)
|
|
786
806
|
})
|
|
787
807
|
|
|
788
808
|
_View.displayName = 'mpx-view'
|
|
@@ -47,7 +47,7 @@ interface FormRef {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const _WebView = forwardRef<HandlerRef<WebView, WebViewProps>, WebViewProps>((props, ref): JSX.Element => {
|
|
50
|
-
const { src, bindmessage = noop, bindload = noop, binderror = noop } = props
|
|
50
|
+
const { src = '', bindmessage = noop, bindload = noop, binderror = noop } = props
|
|
51
51
|
if (props.style) {
|
|
52
52
|
warn('The web-view component does not support the style prop.')
|
|
53
53
|
}
|