@mpxjs/webpack-plugin 2.10.7 → 2.10.8-beta.1
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/dependencies/RecordPageConfigsMapDependency.js +1 -1
- package/lib/dependencies/RequireExternalDependency.js +61 -0
- package/lib/file-loader.js +3 -2
- package/lib/index.js +55 -9
- package/lib/json-compiler/index.js +1 -0
- package/lib/parser.js +1 -1
- package/lib/platform/json/wx/index.js +43 -25
- package/lib/platform/style/wx/index.js +7 -0
- package/lib/platform/template/wx/component-config/fix-component-name.js +2 -2
- package/lib/platform/template/wx/component-config/index.js +7 -1
- package/lib/platform/template/wx/component-config/page-container.js +19 -0
- package/lib/platform/template/wx/component-config/sticky-header.js +23 -0
- package/lib/platform/template/wx/component-config/sticky-section.js +23 -0
- package/lib/platform/template/wx/component-config/unsupported.js +1 -1
- package/lib/react/LoadAsyncChunkModule.js +74 -0
- package/lib/react/index.js +3 -1
- package/lib/react/processJSON.js +74 -13
- package/lib/react/processScript.js +6 -6
- package/lib/react/script-helper.js +100 -41
- package/lib/runtime/components/react/context.ts +12 -3
- package/lib/runtime/components/react/dist/context.js +4 -1
- package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +135 -0
- package/lib/runtime/components/react/dist/mpx-button.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +8 -6
- package/lib/runtime/components/react/dist/mpx-page-container.jsx +255 -0
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +31 -15
- package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +117 -0
- package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +45 -0
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +3 -2
- package/lib/runtime/components/react/mpx-async-suspense.tsx +180 -0
- package/lib/runtime/components/react/mpx-button.tsx +3 -2
- package/lib/runtime/components/react/mpx-movable-view.tsx +8 -4
- package/lib/runtime/components/react/mpx-page-container.tsx +394 -0
- package/lib/runtime/components/react/mpx-scroll-view.tsx +84 -59
- package/lib/runtime/components/react/mpx-sticky-header.tsx +181 -0
- package/lib/runtime/components/react/mpx-sticky-section.tsx +96 -0
- package/lib/runtime/components/react/mpx-swiper.tsx +4 -2
- package/lib/runtime/components/web/mpx-scroll-view.vue +18 -4
- package/lib/runtime/components/web/mpx-sticky-header.vue +99 -0
- package/lib/runtime/components/web/mpx-sticky-section.vue +15 -0
- package/lib/runtime/optionProcessorReact.d.ts +18 -0
- package/lib/runtime/optionProcessorReact.js +30 -0
- package/lib/script-setup-compiler/index.js +27 -5
- package/lib/template-compiler/bind-this.js +2 -1
- package/lib/template-compiler/compiler.js +27 -6
- package/lib/utils/dom-tag-config.js +18 -4
- package/lib/utils/trans-async-sub-rules.js +19 -0
- package/lib/web/script-helper.js +1 -1
- package/package.json +4 -4
- package/LICENSE +0 -433
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { createElement, forwardRef, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { StyleSheet, Animated, Dimensions, TouchableWithoutFeedback, PanResponder } from 'react-native';
|
|
3
|
+
import Portal from './mpx-portal/index';
|
|
4
|
+
import { usePreventRemove } from '@react-navigation/native';
|
|
5
|
+
import { extendObject, useLayout, useNavigation } from './utils';
|
|
6
|
+
import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
7
|
+
import useNodesRef from './useNodesRef';
|
|
8
|
+
const screenHeight = Dimensions.get('window').height;
|
|
9
|
+
const screenWidth = Dimensions.get('window').width;
|
|
10
|
+
function nextTick(cb) {
|
|
11
|
+
setTimeout(cb, 0);
|
|
12
|
+
}
|
|
13
|
+
export default forwardRef((props, ref) => {
|
|
14
|
+
const { show, duration = 300, 'z-index': zIndex = 100, overlay = true, position = 'bottom', round = false, 'close-on-slide-down': closeOnSlideDown = false, 'overlay-style': overlayStyle, 'custom-style': customStyle, bindclose, // RN下特有属性,用于同步show状态到父组件
|
|
15
|
+
bindbeforeenter, bindenter, bindafterenter, bindbeforeleave, bindleave, bindafterleave, bindclickoverlay, children } = props;
|
|
16
|
+
const isFirstRenderFlag = useRef(true);
|
|
17
|
+
const isFirstRender = isFirstRenderFlag.current;
|
|
18
|
+
if (isFirstRenderFlag.current) {
|
|
19
|
+
isFirstRenderFlag.current = false;
|
|
20
|
+
}
|
|
21
|
+
const close = () => {
|
|
22
|
+
bindclose(getCustomEvent('close', {}, { detail: { value: false, source: 'close' } }, props));
|
|
23
|
+
};
|
|
24
|
+
const [internalVisible, setInternalVisible] = useState(show); // 控制组件是否挂载
|
|
25
|
+
const overlayOpacity = useRef(new Animated.Value(0)).current;
|
|
26
|
+
const contentOpacity = useRef(new Animated.Value(position === 'center' ? 0 : 1)).current;
|
|
27
|
+
const contentTranslate = useRef(new Animated.Value(getInitialPosition())).current;
|
|
28
|
+
const currentAnimation = useRef(null);
|
|
29
|
+
function getInitialPosition() {
|
|
30
|
+
switch (position) {
|
|
31
|
+
case 'top':
|
|
32
|
+
return -screenHeight;
|
|
33
|
+
case 'bottom':
|
|
34
|
+
return screenHeight;
|
|
35
|
+
case 'right':
|
|
36
|
+
return screenWidth;
|
|
37
|
+
case 'center':
|
|
38
|
+
return 0;
|
|
39
|
+
default:
|
|
40
|
+
return screenHeight;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const currentTick = useRef(0);
|
|
44
|
+
function createTick() {
|
|
45
|
+
currentTick.current++;
|
|
46
|
+
console.log('currentTick.current++', currentTick.current);
|
|
47
|
+
const current = currentTick.current;
|
|
48
|
+
return () => {
|
|
49
|
+
console.log('currentTick.current', currentTick.current, 'current', current);
|
|
50
|
+
return currentTick.current === current;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// 播放入场动画
|
|
54
|
+
const animateIn = () => {
|
|
55
|
+
const isCurrentTick = createTick();
|
|
56
|
+
const animateOutFinish = currentAnimation.current === null;
|
|
57
|
+
if (!animateOutFinish) {
|
|
58
|
+
currentAnimation.current.forEach((animation) => animation.stop());
|
|
59
|
+
}
|
|
60
|
+
const animations = [
|
|
61
|
+
Animated.timing(contentTranslate, {
|
|
62
|
+
toValue: 0,
|
|
63
|
+
duration,
|
|
64
|
+
useNativeDriver: true
|
|
65
|
+
}),
|
|
66
|
+
Animated.timing(contentOpacity, {
|
|
67
|
+
toValue: 1,
|
|
68
|
+
duration,
|
|
69
|
+
useNativeDriver: true
|
|
70
|
+
}),
|
|
71
|
+
Animated.timing(overlayOpacity, {
|
|
72
|
+
toValue: 1,
|
|
73
|
+
duration,
|
|
74
|
+
useNativeDriver: true
|
|
75
|
+
})
|
|
76
|
+
];
|
|
77
|
+
currentAnimation.current = animations;
|
|
78
|
+
// 所有生命周期需相隔一个nextTick以保证在生命周期中修改show可在组件内部监听到
|
|
79
|
+
bindbeforeenter && bindbeforeenter(getCustomEvent('beforeenter', {}, { detail: { value: false, source: 'beforeenter' } }, props));
|
|
80
|
+
nextTick(() => {
|
|
81
|
+
bindenter && bindenter(getCustomEvent('enter', {}, { detail: { value: false, source: 'enter' } }, props));
|
|
82
|
+
// 与微信对其, bindenter 需要执行,所以 isCurrentTick 放在后面
|
|
83
|
+
if (!isCurrentTick())
|
|
84
|
+
return;
|
|
85
|
+
console.log('animateIn start');
|
|
86
|
+
// 设置为动画初始状态(特殊情况, 如果退场动画没有结束 或者 退场动画还未开始,则无需初始化,而是从当前位置完成动画)
|
|
87
|
+
if (animateOutFinish) {
|
|
88
|
+
contentTranslate.setValue(getInitialPosition());
|
|
89
|
+
contentOpacity.setValue(position === 'center' ? 0 : 1);
|
|
90
|
+
}
|
|
91
|
+
Animated.parallel(animations).start(() => {
|
|
92
|
+
bindafterenter && bindafterenter(getCustomEvent('afterenter', {}, { detail: { value: false, source: 'afterenter' } }, props));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
// 播放离场动画
|
|
97
|
+
const animateOut = () => {
|
|
98
|
+
const isCurrentTick = createTick();
|
|
99
|
+
// 停止入场动画
|
|
100
|
+
currentAnimation.current?.forEach((animation) => animation.stop());
|
|
101
|
+
const animations = [Animated.timing(overlayOpacity, {
|
|
102
|
+
toValue: 0,
|
|
103
|
+
duration,
|
|
104
|
+
useNativeDriver: true
|
|
105
|
+
})
|
|
106
|
+
];
|
|
107
|
+
if (position === 'center') {
|
|
108
|
+
animations.push(Animated.timing(contentOpacity, {
|
|
109
|
+
toValue: 0,
|
|
110
|
+
duration,
|
|
111
|
+
useNativeDriver: true
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
animations.push(Animated.timing(contentTranslate, {
|
|
116
|
+
toValue: getInitialPosition(),
|
|
117
|
+
duration,
|
|
118
|
+
useNativeDriver: true
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
currentAnimation.current = animations;
|
|
122
|
+
bindbeforeleave && bindbeforeleave(getCustomEvent('beforeleave', {}, { detail: { value: false, source: 'beforeleave' } }, props));
|
|
123
|
+
nextTick(() => {
|
|
124
|
+
bindleave && bindleave(getCustomEvent('leave', {}, { detail: { value: false, source: 'leave' } }, props));
|
|
125
|
+
if (!isCurrentTick())
|
|
126
|
+
return;
|
|
127
|
+
console.log('animateOut start');
|
|
128
|
+
Animated.parallel(animations).start(() => {
|
|
129
|
+
currentAnimation.current = null;
|
|
130
|
+
bindafterleave && bindafterleave(getCustomEvent('afterleave', {}, { detail: { value: false, source: 'afterleave' } }, props));
|
|
131
|
+
setInternalVisible(false); // 动画播放完后,才卸载
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
console.log('====comp show', show, 'internalVisible', internalVisible);
|
|
137
|
+
// 如果展示状态和挂载状态一致,则不需要做任何操作
|
|
138
|
+
if (show) {
|
|
139
|
+
setInternalVisible(true); // 确保挂载
|
|
140
|
+
animateIn();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
if (!isFirstRender)
|
|
144
|
+
animateOut();
|
|
145
|
+
}
|
|
146
|
+
}, [show]);
|
|
147
|
+
const navigation = useNavigation();
|
|
148
|
+
usePreventRemove(show, (event) => {
|
|
149
|
+
const { data } = event;
|
|
150
|
+
if (show) {
|
|
151
|
+
close();
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
navigation?.dispatch(data.action);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// IOS 下需要关闭手势返回(原因: IOS手势返回时页面会跟随手指滑动,但是实际返回动作是在松手时触发,需禁掉页面跟随手指滑动的效果)
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
navigation?.setOptions({
|
|
160
|
+
gestureEnabled: !show
|
|
161
|
+
});
|
|
162
|
+
}, [show]);
|
|
163
|
+
const SCREEN_EDGE_THRESHOLD = 60; // 从屏幕左侧 30px 内触发
|
|
164
|
+
// 内容区 手势下滑关闭
|
|
165
|
+
const contentPanResponder = PanResponder.create({
|
|
166
|
+
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
167
|
+
const { dx, dy } = gestureState;
|
|
168
|
+
return dy > 200 && Math.abs(dx) < 60;
|
|
169
|
+
},
|
|
170
|
+
onPanResponderRelease: () => {
|
|
171
|
+
close();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
// 全屏幕 IOS 右滑手势返回
|
|
175
|
+
const screenPanResponder = PanResponder.create({
|
|
176
|
+
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
177
|
+
const { moveX, dx, dy } = gestureState;
|
|
178
|
+
const isFromEdge = moveX < SCREEN_EDGE_THRESHOLD;
|
|
179
|
+
const isHorizontalSwipe = dx > 10 && Math.abs(dy) < 20;
|
|
180
|
+
return isFromEdge && isHorizontalSwipe;
|
|
181
|
+
},
|
|
182
|
+
onPanResponderRelease: (_, gestureState) => {
|
|
183
|
+
if (gestureState.dx > 100) {
|
|
184
|
+
close();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
const getTransformStyle = () => {
|
|
189
|
+
switch (position) {
|
|
190
|
+
case 'top':
|
|
191
|
+
case 'bottom':
|
|
192
|
+
return { transform: [{ translateY: contentTranslate }] };
|
|
193
|
+
case 'right':
|
|
194
|
+
return { transform: [{ translateX: contentTranslate }] };
|
|
195
|
+
case 'center':
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const renderMask = () => {
|
|
200
|
+
const onPress = () => {
|
|
201
|
+
close();
|
|
202
|
+
bindclickoverlay && bindclickoverlay(getCustomEvent('clickoverlay', {}, { detail: { value: false, source: 'clickoverlay' } }, props));
|
|
203
|
+
};
|
|
204
|
+
return createElement(TouchableWithoutFeedback, { onPress }, createElement(Animated.View, { style: [styles.overlay, overlayStyle, { opacity: overlayOpacity }] }));
|
|
205
|
+
};
|
|
206
|
+
const renderContent = (children) => {
|
|
207
|
+
const contentProps = extendObject({
|
|
208
|
+
style: [
|
|
209
|
+
styles.container,
|
|
210
|
+
round ? styles.rounded : null,
|
|
211
|
+
positionStyle[position],
|
|
212
|
+
customStyle,
|
|
213
|
+
getTransformStyle(),
|
|
214
|
+
{ opacity: contentOpacity }
|
|
215
|
+
]
|
|
216
|
+
}, closeOnSlideDown ? contentPanResponder.panHandlers : null);
|
|
217
|
+
return createElement(Animated.View, contentProps, children);
|
|
218
|
+
};
|
|
219
|
+
const nodeRef = useRef(null);
|
|
220
|
+
useNodesRef(props, ref, nodeRef, {});
|
|
221
|
+
const { layoutRef, layoutProps } = useLayout({ props, hasSelfPercent: false, nodeRef });
|
|
222
|
+
const innerProps = useInnerProps(extendObject({}, props, {
|
|
223
|
+
ref: nodeRef
|
|
224
|
+
}, layoutProps), [], { layoutRef });
|
|
225
|
+
const wrapperProps = extendObject(innerProps, {
|
|
226
|
+
style: [styles.wrapper, { zIndex }]
|
|
227
|
+
}, __mpx_mode__ === 'ios' ? screenPanResponder.panHandlers : {});
|
|
228
|
+
// TODO 是否有必要支持refs? dataset?
|
|
229
|
+
return createElement(Portal, null, internalVisible
|
|
230
|
+
? createElement(Animated.View, wrapperProps, overlay ? renderMask() : null, renderContent(children))
|
|
231
|
+
: null);
|
|
232
|
+
});
|
|
233
|
+
const styles = StyleSheet.create({
|
|
234
|
+
wrapper: extendObject({
|
|
235
|
+
justifyContent: 'flex-end',
|
|
236
|
+
alignItems: 'center'
|
|
237
|
+
}, StyleSheet.absoluteFillObject),
|
|
238
|
+
overlay: extendObject({
|
|
239
|
+
backgroundColor: 'rgba(0,0,0,0.5)'
|
|
240
|
+
}, StyleSheet.absoluteFillObject),
|
|
241
|
+
container: {
|
|
242
|
+
position: 'absolute',
|
|
243
|
+
backgroundColor: 'white'
|
|
244
|
+
},
|
|
245
|
+
rounded: {
|
|
246
|
+
borderTopLeftRadius: 20,
|
|
247
|
+
borderTopRightRadius: 20
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
const positionStyle = {
|
|
251
|
+
bottom: { bottom: 0, width: '100%', height: 'auto' },
|
|
252
|
+
top: { top: 0, width: '100%', height: 'auto' },
|
|
253
|
+
right: extendObject({}, StyleSheet.absoluteFillObject, { right: 0 }),
|
|
254
|
+
center: extendObject({}, StyleSheet.absoluteFillObject)
|
|
255
|
+
};
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
* ✔ bindscroll
|
|
33
33
|
*/
|
|
34
34
|
import { ScrollView, RefreshControl, Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
35
|
+
import { Animated as RNAnimated } from 'react-native';
|
|
35
36
|
import { isValidElement, Children, useRef, useState, useEffect, forwardRef, useContext, useMemo, createElement } from 'react';
|
|
36
37
|
import Animated, { useAnimatedRef, useSharedValue, withTiming, useAnimatedStyle, runOnJS } from 'react-native-reanimated';
|
|
37
38
|
import { warn, hasOwn } from '@mpxjs/utils';
|
|
@@ -40,9 +41,11 @@ import useNodesRef from './useNodesRef';
|
|
|
40
41
|
import { splitProps, splitStyle, useTransformStyle, useLayout, wrapChildren, extendObject, flatGesture, HIDDEN_STYLE } from './utils';
|
|
41
42
|
import { IntersectionObserverContext, ScrollViewContext } from './context';
|
|
42
43
|
import Portal from './mpx-portal';
|
|
44
|
+
const AnimatedScrollView = RNAnimated.createAnimatedComponent(ScrollView);
|
|
43
45
|
const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
44
46
|
const { textProps, innerProps: props = {} } = splitProps(scrollViewProps);
|
|
45
|
-
const { enhanced = false, bounces = true, style = {}, binddragstart, binddragging, binddragend, bindtouchstart, bindtouchmove, bindtouchend, 'scroll-x': scrollX = false, 'scroll-y': scrollY = false, 'enable-back-to-top': enableBackToTop = false, 'enable-trigger-intersection-observer': enableTriggerIntersectionObserver = false, 'paging-enabled': pagingEnabled = false, 'upper-threshold': upperThreshold = 50, 'lower-threshold': lowerThreshold = 50, 'scroll-with-animation': scrollWithAnimation = false, 'refresher-enabled': refresherEnabled, 'refresher-default-style': refresherDefaultStyle, 'refresher-background': refresherBackground, 'refresher-threshold': refresherThreshold = 45, 'show-scrollbar': showScrollbar = true, 'scroll-into-view': scrollIntoView = '', 'scroll-top': scrollTop = 0, 'scroll-left': scrollLeft = 0, 'refresher-triggered': refresherTriggered, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'simultaneous-handlers': originSimultaneousHandlers, 'wait-for': waitFor, 'scroll-event-throttle': scrollEventThrottle = 0, __selectRef } = props;
|
|
47
|
+
const { enhanced = false, bounces = true, style = {}, binddragstart, binddragging, binddragend, bindtouchstart, bindtouchmove, bindtouchend, 'scroll-x': scrollX = false, 'scroll-y': scrollY = false, 'enable-back-to-top': enableBackToTop = false, 'enable-trigger-intersection-observer': enableTriggerIntersectionObserver = false, 'paging-enabled': pagingEnabled = false, 'upper-threshold': upperThreshold = 50, 'lower-threshold': lowerThreshold = 50, 'scroll-with-animation': scrollWithAnimation = false, 'refresher-enabled': refresherEnabled, 'refresher-default-style': refresherDefaultStyle, 'refresher-background': refresherBackground, 'refresher-threshold': refresherThreshold = 45, 'show-scrollbar': showScrollbar = true, 'scroll-into-view': scrollIntoView = '', 'scroll-top': scrollTop = 0, 'scroll-left': scrollLeft = 0, 'refresher-triggered': refresherTriggered, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'simultaneous-handlers': originSimultaneousHandlers, 'wait-for': waitFor, 'enable-sticky': enableSticky, 'scroll-event-throttle': scrollEventThrottle = 0, 'scroll-into-view-offset': scrollIntoViewOffset = 0, __selectRef } = props;
|
|
48
|
+
const scrollOffset = useRef(new RNAnimated.Value(0)).current;
|
|
46
49
|
const simultaneousHandlers = flatGesture(originSimultaneousHandlers);
|
|
47
50
|
const waitForHandlers = flatGesture(waitFor);
|
|
48
51
|
const snapScrollTop = useRef(0);
|
|
@@ -66,7 +69,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
66
69
|
const hasCallScrollToLower = useRef(false);
|
|
67
70
|
const initialTimeout = useRef(null);
|
|
68
71
|
const intersectionObservers = useContext(IntersectionObserverContext);
|
|
69
|
-
const firstScrollIntoViewChange = useRef(
|
|
72
|
+
const firstScrollIntoViewChange = useRef(true);
|
|
70
73
|
const refreshColor = {
|
|
71
74
|
black: ['#000'],
|
|
72
75
|
white: ['#fff']
|
|
@@ -86,16 +89,18 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
86
89
|
pagingEnabled,
|
|
87
90
|
fastDeceleration: false,
|
|
88
91
|
decelerationDisabled: false,
|
|
89
|
-
scrollTo
|
|
92
|
+
scrollTo,
|
|
93
|
+
scrollIntoView: handleScrollIntoView
|
|
90
94
|
},
|
|
91
95
|
gestureRef: scrollViewRef
|
|
92
96
|
});
|
|
97
|
+
const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef, onLayout });
|
|
93
98
|
const contextValue = useMemo(() => {
|
|
94
99
|
return {
|
|
95
|
-
gestureRef: scrollViewRef
|
|
100
|
+
gestureRef: scrollViewRef,
|
|
101
|
+
scrollOffset
|
|
96
102
|
};
|
|
97
103
|
}, []);
|
|
98
|
-
const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef, onLayout });
|
|
99
104
|
const hasRefresherLayoutRef = useRef(false);
|
|
100
105
|
// layout 完成前先隐藏,避免安卓闪烁问题
|
|
101
106
|
const refresherLayoutStyle = useMemo(() => { return !hasRefresherLayoutRef.current ? HIDDEN_STYLE : {}; }, [hasRefresherLayoutRef.current]);
|
|
@@ -115,14 +120,16 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
115
120
|
}, [scrollTop, scrollLeft]);
|
|
116
121
|
useEffect(() => {
|
|
117
122
|
if (scrollIntoView && __selectRef) {
|
|
118
|
-
if (
|
|
119
|
-
setTimeout(
|
|
123
|
+
if (firstScrollIntoViewChange.current) {
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
handleScrollIntoView(scrollIntoView, { offset: scrollIntoViewOffset, animated: scrollWithAnimation });
|
|
126
|
+
});
|
|
120
127
|
}
|
|
121
128
|
else {
|
|
122
|
-
handleScrollIntoView();
|
|
129
|
+
handleScrollIntoView(scrollIntoView, { offset: scrollIntoViewOffset, animated: scrollWithAnimation });
|
|
123
130
|
}
|
|
124
131
|
}
|
|
125
|
-
firstScrollIntoViewChange.current =
|
|
132
|
+
firstScrollIntoViewChange.current = false;
|
|
126
133
|
}, [scrollIntoView]);
|
|
127
134
|
useEffect(() => {
|
|
128
135
|
if (refresherEnabled) {
|
|
@@ -142,13 +149,15 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
142
149
|
function scrollTo({ top = 0, left = 0, animated = false }) {
|
|
143
150
|
scrollToOffset(left, top, animated);
|
|
144
151
|
}
|
|
145
|
-
function handleScrollIntoView() {
|
|
146
|
-
const refs = __selectRef(`#${
|
|
152
|
+
function handleScrollIntoView(selector = '', { offset = 0, animated = true } = {}) {
|
|
153
|
+
const refs = __selectRef(`#${selector}`, 'node');
|
|
147
154
|
if (!refs)
|
|
148
155
|
return;
|
|
149
156
|
const { nodeRef } = refs.getNodeInstance();
|
|
150
157
|
nodeRef.current?.measureLayout(scrollViewRef.current, (left, top) => {
|
|
151
|
-
|
|
158
|
+
const adjustedLeft = scrollX ? left + offset : left;
|
|
159
|
+
const adjustedTop = scrollY ? top + offset : top;
|
|
160
|
+
scrollToOffset(adjustedLeft, adjustedTop, animated);
|
|
152
161
|
});
|
|
153
162
|
}
|
|
154
163
|
function selectLength(size) {
|
|
@@ -321,6 +330,12 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
321
330
|
updateScrollOptions(e, { scrollLeft, scrollTop });
|
|
322
331
|
updateIntersection();
|
|
323
332
|
}
|
|
333
|
+
const scrollHandler = RNAnimated.event([{ nativeEvent: { contentOffset: { y: scrollOffset } } }], {
|
|
334
|
+
useNativeDriver: true,
|
|
335
|
+
listener: (event) => {
|
|
336
|
+
onScroll(event);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
324
339
|
function onScrollDragStart(e) {
|
|
325
340
|
hasCallScrollToLower.current = false;
|
|
326
341
|
hasCallScrollToUpper.current = false;
|
|
@@ -481,7 +496,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
481
496
|
scrollEnabled: !enableScroll ? false : !!(scrollX || scrollY),
|
|
482
497
|
bounces: false,
|
|
483
498
|
ref: scrollViewRef,
|
|
484
|
-
onScroll: onScroll,
|
|
499
|
+
onScroll: enableSticky ? scrollHandler : onScroll,
|
|
485
500
|
onContentSizeChange: onContentSizeChange,
|
|
486
501
|
bindtouchstart: ((enhanced && binddragstart) || bindtouchstart) && onScrollTouchStart,
|
|
487
502
|
bindtouchmove: ((enhanced && binddragging) || bindtouchmove) && onScrollTouchMove,
|
|
@@ -523,13 +538,14 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
523
538
|
'bindscrolltolower',
|
|
524
539
|
'bindrefresherrefresh'
|
|
525
540
|
], { layoutRef });
|
|
526
|
-
const
|
|
541
|
+
const ScrollViewComponent = enableSticky ? AnimatedScrollView : ScrollView;
|
|
542
|
+
const withRefresherScrollView = createElement(GestureDetector, { gesture: panGesture }, createElement(ScrollViewComponent, innerProps, createElement(Animated.View, { style: [refresherAnimatedStyle, refresherLayoutStyle], onLayout: onRefresherLayout }, refresherContent), createElement(Animated.View, { style: contentAnimatedStyle }, createElement(ScrollViewContext.Provider, { value: contextValue }, wrapChildren(extendObject({}, props, { children: otherContent }), {
|
|
527
543
|
hasVarDec,
|
|
528
544
|
varContext: varContextRef.current,
|
|
529
545
|
textStyle,
|
|
530
546
|
textProps
|
|
531
547
|
})))));
|
|
532
|
-
const commonScrollView = createElement(
|
|
548
|
+
const commonScrollView = createElement(ScrollViewComponent, extendObject({}, innerProps, {
|
|
533
549
|
refreshControl: refresherEnabled
|
|
534
550
|
? createElement(RefreshControl, extendObject({
|
|
535
551
|
progressBackgroundColor: refresherBackground,
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useEffect, useRef, useContext, forwardRef, useMemo, createElement, useId } from 'react';
|
|
2
|
+
import { Animated, StyleSheet, useAnimatedValue } from 'react-native';
|
|
3
|
+
import { ScrollViewContext, StickyContext } from './context';
|
|
4
|
+
import useNodesRef from './useNodesRef';
|
|
5
|
+
import { splitProps, splitStyle, useTransformStyle, wrapChildren, useLayout, extendObject } from './utils';
|
|
6
|
+
import { error } from '@mpxjs/utils';
|
|
7
|
+
import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
8
|
+
const _StickyHeader = forwardRef((stickyHeaderProps = {}, ref) => {
|
|
9
|
+
const { textProps, innerProps: props = {} } = splitProps(stickyHeaderProps);
|
|
10
|
+
const { style, bindstickontopchange, padding = [0, 0, 0, 0], 'offset-top': offsetTop = 0, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
11
|
+
const scrollViewContext = useContext(ScrollViewContext);
|
|
12
|
+
const stickyContext = useContext(StickyContext);
|
|
13
|
+
const { scrollOffset } = scrollViewContext;
|
|
14
|
+
const { registerStickyHeader, unregisterStickyHeader } = stickyContext;
|
|
15
|
+
const headerRef = useRef(null);
|
|
16
|
+
const isStickOnTopRef = useRef(false);
|
|
17
|
+
const id = useId();
|
|
18
|
+
const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
19
|
+
const { layoutRef, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: headerRef, onLayout });
|
|
20
|
+
const { textStyle, innerStyle = {} } = splitStyle(normalStyle);
|
|
21
|
+
const headerTopAnimated = useAnimatedValue(0);
|
|
22
|
+
// harmony animatedValue 不支持通过 _value 访问
|
|
23
|
+
const headerTopRef = useRef(0);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
registerStickyHeader({ key: id, updatePosition });
|
|
26
|
+
return () => {
|
|
27
|
+
unregisterStickyHeader(id);
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
function updatePosition() {
|
|
31
|
+
if (headerRef.current) {
|
|
32
|
+
const scrollViewRef = scrollViewContext.gestureRef;
|
|
33
|
+
if (scrollViewRef && scrollViewRef.current) {
|
|
34
|
+
headerRef.current.measureLayout(scrollViewRef.current, (left, top) => {
|
|
35
|
+
Animated.timing(headerTopAnimated, {
|
|
36
|
+
toValue: top,
|
|
37
|
+
duration: 0,
|
|
38
|
+
useNativeDriver: true
|
|
39
|
+
}).start();
|
|
40
|
+
headerTopRef.current = top;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
error('StickyHeader measureLayout error: scrollViewRef is not a valid native component reference');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function onLayout(e) {
|
|
49
|
+
updatePosition();
|
|
50
|
+
}
|
|
51
|
+
useNodesRef(props, ref, headerRef, {
|
|
52
|
+
style: normalStyle
|
|
53
|
+
});
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!bindstickontopchange)
|
|
56
|
+
return;
|
|
57
|
+
const listener = scrollOffset.addListener((state) => {
|
|
58
|
+
const currentScrollValue = state.value;
|
|
59
|
+
const newIsStickOnTop = currentScrollValue > headerTopRef.current;
|
|
60
|
+
if (newIsStickOnTop !== isStickOnTopRef.current) {
|
|
61
|
+
isStickOnTopRef.current = newIsStickOnTop;
|
|
62
|
+
bindstickontopchange(getCustomEvent('stickontopchange', {}, {
|
|
63
|
+
detail: {
|
|
64
|
+
isStickOnTop: newIsStickOnTop
|
|
65
|
+
},
|
|
66
|
+
layoutRef
|
|
67
|
+
}, props));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return () => {
|
|
71
|
+
scrollOffset.removeListener(listener);
|
|
72
|
+
};
|
|
73
|
+
}, []);
|
|
74
|
+
const animatedStyle = useMemo(() => {
|
|
75
|
+
const translateY = Animated.subtract(scrollOffset, headerTopAnimated).interpolate({
|
|
76
|
+
inputRange: [0, 1],
|
|
77
|
+
outputRange: [0, 1],
|
|
78
|
+
extrapolateLeft: 'clamp',
|
|
79
|
+
extrapolateRight: 'extend'
|
|
80
|
+
});
|
|
81
|
+
const finalTranslateY = offsetTop === 0
|
|
82
|
+
? translateY
|
|
83
|
+
: Animated.add(translateY, Animated.subtract(scrollOffset, headerTopAnimated).interpolate({
|
|
84
|
+
inputRange: [0, 1],
|
|
85
|
+
outputRange: [0, offsetTop],
|
|
86
|
+
extrapolate: 'clamp'
|
|
87
|
+
}));
|
|
88
|
+
return {
|
|
89
|
+
transform: [{ translateY: finalTranslateY }]
|
|
90
|
+
};
|
|
91
|
+
}, [scrollOffset, headerTopAnimated, offsetTop]);
|
|
92
|
+
const innerProps = useInnerProps(extendObject({}, props, {
|
|
93
|
+
ref: headerRef,
|
|
94
|
+
style: extendObject({}, styles.content, innerStyle, animatedStyle, {
|
|
95
|
+
paddingTop: padding[0] || 0,
|
|
96
|
+
paddingRight: padding[1] || 0,
|
|
97
|
+
paddingBottom: padding[2] || 0,
|
|
98
|
+
paddingLeft: padding[3] || 0
|
|
99
|
+
})
|
|
100
|
+
}, layoutProps), [], { layoutRef });
|
|
101
|
+
return (createElement(Animated.View, innerProps, wrapChildren(props, {
|
|
102
|
+
hasVarDec,
|
|
103
|
+
varContext: varContextRef.current,
|
|
104
|
+
textStyle,
|
|
105
|
+
textProps
|
|
106
|
+
})));
|
|
107
|
+
});
|
|
108
|
+
const styles = StyleSheet.create({
|
|
109
|
+
content: {
|
|
110
|
+
width: '100%',
|
|
111
|
+
zIndex: 10,
|
|
112
|
+
// harmony 需要手动设置 relative, zIndex 才生效
|
|
113
|
+
position: 'relative'
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
_StickyHeader.displayName = 'MpxStickyHeader';
|
|
117
|
+
export default _StickyHeader;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useRef, forwardRef, createElement, useCallback, useMemo } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import useNodesRef from './useNodesRef';
|
|
4
|
+
import { splitProps, splitStyle, useTransformStyle, wrapChildren, useLayout, extendObject } from './utils';
|
|
5
|
+
import { StickyContext } from './context';
|
|
6
|
+
import useInnerProps from './getInnerListeners';
|
|
7
|
+
const _StickySection = forwardRef((stickySectionProps = {}, ref) => {
|
|
8
|
+
const { textProps, innerProps: props = {} } = splitProps(stickySectionProps);
|
|
9
|
+
const { style, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
10
|
+
const sectionRef = useRef(null);
|
|
11
|
+
const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
12
|
+
const { layoutRef, layoutProps, layoutStyle } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: sectionRef, onLayout });
|
|
13
|
+
const { textStyle, innerStyle = {} } = splitStyle(normalStyle);
|
|
14
|
+
const stickyHeaders = useRef(new Map());
|
|
15
|
+
const registerStickyHeader = useCallback((item) => {
|
|
16
|
+
stickyHeaders.current.set(item.id, item);
|
|
17
|
+
}, []);
|
|
18
|
+
const unregisterStickyHeader = useCallback((id) => {
|
|
19
|
+
stickyHeaders.current.delete(id);
|
|
20
|
+
}, []);
|
|
21
|
+
const contextValue = useMemo(() => ({
|
|
22
|
+
registerStickyHeader,
|
|
23
|
+
unregisterStickyHeader
|
|
24
|
+
}), []);
|
|
25
|
+
useNodesRef(props, ref, sectionRef, {
|
|
26
|
+
style: normalStyle
|
|
27
|
+
});
|
|
28
|
+
function onLayout() {
|
|
29
|
+
stickyHeaders.current.forEach(item => {
|
|
30
|
+
item.updatePosition();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const innerProps = useInnerProps(extendObject({}, props, {
|
|
34
|
+
style: extendObject(innerStyle, layoutStyle),
|
|
35
|
+
ref: sectionRef
|
|
36
|
+
}, layoutProps), [], { layoutRef });
|
|
37
|
+
return (createElement(View, innerProps, createElement(StickyContext.Provider, { value: contextValue }, wrapChildren(props, {
|
|
38
|
+
hasVarDec,
|
|
39
|
+
varContext: varContextRef.current,
|
|
40
|
+
textStyle,
|
|
41
|
+
textProps
|
|
42
|
+
}))));
|
|
43
|
+
});
|
|
44
|
+
_StickySection.displayName = 'MpxStickySection';
|
|
45
|
+
export default _StickySection;
|
|
@@ -71,7 +71,7 @@ const easeMap = {
|
|
|
71
71
|
easeInOutCubic: Easing.inOut(Easing.cubic)
|
|
72
72
|
};
|
|
73
73
|
const SwiperWrapper = forwardRef((props, ref) => {
|
|
74
|
-
const { 'indicator-dots': showPagination, 'indicator-color': dotColor = 'rgba(0, 0, 0, .3)', 'indicator-active-color': activeDotColor = '#000000', 'enable-var': enableVar = false, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'external-var-context': externalVarContext, 'simultaneous-handlers': originSimultaneousHandlers = [], 'wait-for': waitFor = [], style = {}, autoplay = false, circular = false, disableGesture = false } = props;
|
|
74
|
+
const { 'indicator-dots': showPagination, 'indicator-color': dotColor = 'rgba(0, 0, 0, .3)', 'indicator-active-color': activeDotColor = '#000000', 'enable-var': enableVar = false, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'external-var-context': externalVarContext, 'simultaneous-handlers': originSimultaneousHandlers = [], 'wait-for': waitFor = [], style = {}, autoplay = false, circular = false, disableGesture = false, bindchange } = props;
|
|
75
75
|
const easeingFunc = props['easing-function'] || 'default';
|
|
76
76
|
const easeDuration = props.duration || 500;
|
|
77
77
|
const horizontal = props.vertical !== undefined ? !props.vertical : true;
|
|
@@ -330,6 +330,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
330
330
|
if (props.current !== currentIndex.value) {
|
|
331
331
|
const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef });
|
|
332
332
|
props.bindchange && props.bindchange(eventData);
|
|
333
|
+
bindchange && bindchange(eventData);
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
336
|
function getOffset(index, stepValue) {
|
|
@@ -373,7 +374,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
373
374
|
// 1. 用户在当前页切换选中项,动画;用户携带选中index打开到swiper页直接选中不走动画
|
|
374
375
|
useAnimatedReaction(() => currentIndex.value, (newIndex, preIndex) => {
|
|
375
376
|
// 这里必须传递函数名, 直接写()=> {}形式会报 访问了未sharedValue信息
|
|
376
|
-
if (newIndex !== preIndex &&
|
|
377
|
+
if (newIndex !== preIndex && bindchange) {
|
|
377
378
|
runOnJS(handleSwiperChange)(newIndex);
|
|
378
379
|
}
|
|
379
380
|
});
|