@mpxjs/webpack-plugin 2.10.7-beta.9 → 2.10.8
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/LICENSE +433 -0
- package/lib/dependencies/RequireExternalDependency.js +61 -0
- package/lib/file-loader.js +3 -2
- package/lib/index.js +60 -15
- 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/template/wx/component-config/fix-component-name.js +2 -2
- package/lib/platform/template/wx/component-config/movable-view.js +10 -1
- package/lib/platform/template/wx/index.js +2 -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 +2 -12
- package/lib/runtime/components/react/dist/context.js +1 -1
- package/lib/runtime/components/react/dist/getInnerListeners.js +1 -1
- package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +135 -0
- package/lib/runtime/components/react/dist/mpx-movable-area.jsx +9 -63
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +58 -301
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +27 -53
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +14 -28
- package/lib/runtime/components/react/dist/useAnimationHooks.js +2 -87
- package/lib/runtime/components/react/getInnerListeners.ts +1 -1
- package/lib/runtime/components/react/mpx-async-suspense.tsx +180 -0
- package/lib/runtime/components/react/mpx-movable-area.tsx +11 -98
- package/lib/runtime/components/react/mpx-movable-view.tsx +60 -350
- package/lib/runtime/components/react/mpx-swiper.tsx +25 -53
- package/lib/runtime/components/react/mpx-web-view.tsx +13 -33
- package/lib/runtime/components/react/types/global.d.ts +15 -0
- package/lib/runtime/components/react/useAnimationHooks.ts +2 -85
- 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/compiler.js +4 -3
- package/lib/utils/dom-tag-config.js +17 -3
- package/lib/utils/trans-async-sub-rules.js +19 -0
- package/lib/web/script-helper.js +1 -1
- package/package.json +4 -4
- package/lib/runtime/components/react/AsyncContainer.tsx +0 -189
- package/lib/runtime/components/react/dist/AsyncContainer.jsx +0 -141
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { forwardRef, useRef, useContext, useMemo, useState } from 'react';
|
|
2
2
|
import { warn, isFunction } from '@mpxjs/utils';
|
|
3
3
|
import Portal from './mpx-portal/index';
|
|
4
|
+
import { usePreventRemove } from '@react-navigation/native';
|
|
4
5
|
import { getCustomEvent } from './getInnerListeners';
|
|
5
6
|
import { promisify, redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy';
|
|
6
7
|
import { WebView } from 'react-native-webview';
|
|
@@ -55,8 +56,8 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
55
56
|
const webViewRef = useRef(null);
|
|
56
57
|
const fristLoaded = useRef(false);
|
|
57
58
|
const isLoadError = useRef(false);
|
|
59
|
+
const isNavigateBack = useRef(false);
|
|
58
60
|
const statusCode = useRef('');
|
|
59
|
-
const [isLoaded, setIsLoaded] = useState(true);
|
|
60
61
|
const defaultWebViewStyle = {
|
|
61
62
|
position: 'absolute',
|
|
62
63
|
left: 0,
|
|
@@ -64,27 +65,18 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
64
65
|
top: 0,
|
|
65
66
|
bottom: 0
|
|
66
67
|
};
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const navigation = useNavigation();
|
|
69
|
+
const [isIntercept, setIsIntercept] = useState(false);
|
|
70
|
+
usePreventRemove(isIntercept, (event) => {
|
|
71
|
+
const { data } = event;
|
|
72
|
+
if (isNavigateBack.current) {
|
|
73
|
+
navigation?.dispatch(data.action);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
71
76
|
webViewRef.current?.goBack();
|
|
72
|
-
e.preventDefault();
|
|
73
77
|
}
|
|
74
78
|
isNavigateBack.current = false;
|
|
75
|
-
};
|
|
76
|
-
const navigation = useNavigation();
|
|
77
|
-
// useEffect(() => {
|
|
78
|
-
// let beforeRemoveSubscription:any
|
|
79
|
-
// if (__mpx_mode__ !== 'ios') {
|
|
80
|
-
// beforeRemoveSubscription = navigation?.addListener?.('beforeRemove', beforeRemoveHandle)
|
|
81
|
-
// }
|
|
82
|
-
// return () => {
|
|
83
|
-
// if (isFunction(beforeRemoveSubscription)) {
|
|
84
|
-
// beforeRemoveSubscription()
|
|
85
|
-
// }
|
|
86
|
-
// }
|
|
87
|
-
// }, [])
|
|
79
|
+
});
|
|
88
80
|
useNodesRef(props, ref, webViewRef, {
|
|
89
81
|
style: defaultWebViewStyle
|
|
90
82
|
});
|
|
@@ -131,13 +123,13 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
131
123
|
};
|
|
132
124
|
const _changeUrl = function (navState) {
|
|
133
125
|
if (navState.navigationType) { // navigationType这个事件在页面开始加载时和页面加载完成时都会被触发所以判断这个避免其他无效触发执行该逻辑
|
|
134
|
-
canGoBack.current = navState.canGoBack;
|
|
135
126
|
currentPage.__webViewUrl = navState.url;
|
|
127
|
+
setIsIntercept(navState.canGoBack);
|
|
136
128
|
}
|
|
137
129
|
};
|
|
138
130
|
const _onLoadProgress = function (event) {
|
|
139
131
|
if (__mpx_mode__ !== 'ios') {
|
|
140
|
-
|
|
132
|
+
setIsIntercept(event.nativeEvent.canGoBack);
|
|
141
133
|
}
|
|
142
134
|
};
|
|
143
135
|
const _message = function (res) {
|
|
@@ -227,7 +219,6 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
227
219
|
};
|
|
228
220
|
const onLoadEndHandle = function (res) {
|
|
229
221
|
fristLoaded.current = true;
|
|
230
|
-
setIsLoaded(true);
|
|
231
222
|
const src = res.nativeEvent?.url;
|
|
232
223
|
if (isLoadError.current) {
|
|
233
224
|
isLoadError.current = false;
|
|
@@ -275,18 +266,13 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
275
266
|
setPageLoadErr(true);
|
|
276
267
|
}
|
|
277
268
|
};
|
|
278
|
-
const onLoadStart = function () {
|
|
279
|
-
if (!fristLoaded.current) {
|
|
280
|
-
setIsLoaded(false);
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
269
|
return (<Portal>
|
|
284
270
|
{pageLoadErr
|
|
285
271
|
? (<View style={[styles.loadErrorContext, defaultWebViewStyle]}>
|
|
286
272
|
<View style={styles.loadErrorText}><Text style={{ fontSize: 14, color: '#999999' }}>{currentErrorText.text}</Text></View>
|
|
287
273
|
<View style={styles.loadErrorButton} onTouchEnd={_reload}><Text style={{ fontSize: 12, color: '#666666' }}>{currentErrorText.button}</Text></View>
|
|
288
274
|
</View>)
|
|
289
|
-
: (<WebView style={defaultWebViewStyle}
|
|
275
|
+
: (<WebView style={defaultWebViewStyle} source={{ uri: src }} ref={webViewRef} javaScriptEnabled={true} onNavigationStateChange={_changeUrl} onMessage={_message} injectedJavaScript={injectedJavaScript} onLoadProgress={_onLoadProgress} onLoadEnd={onLoadEnd} onHttpError={onHttpError} onError={onError} allowsBackForwardNavigationGestures={true}></WebView>)}
|
|
290
276
|
</Portal>);
|
|
291
277
|
});
|
|
292
278
|
_WebView.displayName = 'MpxWebview';
|
|
@@ -48,90 +48,6 @@ const InitialValue = Object.assign({
|
|
|
48
48
|
const TransformOrigin = 'transformOrigin';
|
|
49
49
|
// transform
|
|
50
50
|
const isTransform = (key) => Object.keys(TransformInitial).includes(key);
|
|
51
|
-
// 多value解析
|
|
52
|
-
const parseValues = (str, char = ' ') => {
|
|
53
|
-
let stack = 0;
|
|
54
|
-
let temp = '';
|
|
55
|
-
const result = [];
|
|
56
|
-
for (let i = 0; i < str.length; i++) {
|
|
57
|
-
if (str[i] === '(') {
|
|
58
|
-
stack++;
|
|
59
|
-
}
|
|
60
|
-
else if (str[i] === ')') {
|
|
61
|
-
stack--;
|
|
62
|
-
}
|
|
63
|
-
// 非括号内 或者 非分隔字符且非空
|
|
64
|
-
if (stack !== 0 || (str[i] !== char && str[i] !== ' ')) {
|
|
65
|
-
temp += str[i];
|
|
66
|
-
}
|
|
67
|
-
if ((stack === 0 && str[i] === char) || i === str.length - 1) {
|
|
68
|
-
result.push(temp);
|
|
69
|
-
temp = '';
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return result;
|
|
73
|
-
};
|
|
74
|
-
// parse string transform, eg: transform: 'rotateX(45deg) rotateZ(0.785398rad)'
|
|
75
|
-
const parseTransform = (transformStr) => {
|
|
76
|
-
const values = parseValues(transformStr);
|
|
77
|
-
const transform = [];
|
|
78
|
-
values.forEach(item => {
|
|
79
|
-
const match = item.match(/([/\w]+)\((.+)\)/);
|
|
80
|
-
if (match && match.length >= 3) {
|
|
81
|
-
let key = match[1];
|
|
82
|
-
const val = match[2];
|
|
83
|
-
switch (key) {
|
|
84
|
-
case 'translateX':
|
|
85
|
-
case 'translateY':
|
|
86
|
-
case 'scaleX':
|
|
87
|
-
case 'scaleY':
|
|
88
|
-
case 'rotateX':
|
|
89
|
-
case 'rotateY':
|
|
90
|
-
case 'rotateZ':
|
|
91
|
-
case 'rotate':
|
|
92
|
-
case 'skewX':
|
|
93
|
-
case 'skewY':
|
|
94
|
-
case 'perspective':
|
|
95
|
-
// rotate 处理成 rotateZ
|
|
96
|
-
key = key === 'rotate' ? 'rotateZ' : key;
|
|
97
|
-
// 单个值处理
|
|
98
|
-
transform.push({ [key]: global.__formatValue(val) });
|
|
99
|
-
break;
|
|
100
|
-
case 'matrix':
|
|
101
|
-
transform.push({ [key]: parseValues(val, ',').map(val => +val) });
|
|
102
|
-
break;
|
|
103
|
-
case 'translate':
|
|
104
|
-
case 'scale':
|
|
105
|
-
case 'skew':
|
|
106
|
-
case 'translate3d': // x y 支持 z不支持
|
|
107
|
-
case 'scale3d': // x y 支持 z不支持
|
|
108
|
-
{
|
|
109
|
-
// 2 个以上的值处理
|
|
110
|
-
key = key.replace('3d', '');
|
|
111
|
-
const vals = parseValues(val, ',').splice(0, 3);
|
|
112
|
-
// scale(.5) === scaleX(.5) scaleY(.5)
|
|
113
|
-
if (vals.length === 1 && key === 'scale') {
|
|
114
|
-
vals.push(vals[0]);
|
|
115
|
-
}
|
|
116
|
-
const xyz = ['X', 'Y', 'Z'];
|
|
117
|
-
transform.push(...vals.map((v, index) => {
|
|
118
|
-
return { [`${key}${xyz[index] || ''}`]: global.__formatValue(v.trim()) };
|
|
119
|
-
}));
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
return transform;
|
|
126
|
-
};
|
|
127
|
-
// format style
|
|
128
|
-
const formatStyle = (style) => {
|
|
129
|
-
if (!style.transform || Array.isArray(style.transform))
|
|
130
|
-
return style;
|
|
131
|
-
return Object.assign({}, style, {
|
|
132
|
-
transform: parseTransform(style.transform)
|
|
133
|
-
});
|
|
134
|
-
};
|
|
135
51
|
// transform 数组转对象
|
|
136
52
|
function getTransformObj(transforms) {
|
|
137
53
|
'worklet';
|
|
@@ -140,7 +56,7 @@ function getTransformObj(transforms) {
|
|
|
140
56
|
}, {});
|
|
141
57
|
}
|
|
142
58
|
export default function useAnimationHooks(props) {
|
|
143
|
-
const { style = {}, animation, enableAnimation, transitionend, layoutRef } = props;
|
|
59
|
+
const { style: originalStyle = {}, animation, enableAnimation, transitionend, layoutRef } = props;
|
|
144
60
|
const enableStyleAnimation = enableAnimation || !!animation;
|
|
145
61
|
const enableAnimationRef = useRef(enableStyleAnimation);
|
|
146
62
|
if (enableAnimationRef.current !== enableStyleAnimation) {
|
|
@@ -148,7 +64,6 @@ export default function useAnimationHooks(props) {
|
|
|
148
64
|
}
|
|
149
65
|
if (!enableAnimationRef.current)
|
|
150
66
|
return { enableStyleAnimation: false };
|
|
151
|
-
const originalStyle = formatStyle(style);
|
|
152
67
|
// id 标识
|
|
153
68
|
const id = animation?.id || -1;
|
|
154
69
|
// 有动画样式的 style key
|
|
@@ -177,7 +92,7 @@ export default function useAnimationHooks(props) {
|
|
|
177
92
|
useEffect(() => {
|
|
178
93
|
// style 更新后同步更新 lastStyleRef & shareValMap
|
|
179
94
|
updateStyleVal();
|
|
180
|
-
}, [
|
|
95
|
+
}, [originalStyle]);
|
|
181
96
|
// ** 获取动画样式prop & 驱动动画
|
|
182
97
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
183
98
|
useEffect(() => {
|
|
@@ -24,7 +24,7 @@ const getTouchEvent = (
|
|
|
24
24
|
) => {
|
|
25
25
|
const { navigation, propsRef, layoutRef } = config
|
|
26
26
|
const props = propsRef.current
|
|
27
|
-
const {
|
|
27
|
+
const { top: navigationY = 0 } = navigation?.layout || {}
|
|
28
28
|
const nativeEvent = event.nativeEvent
|
|
29
29
|
const { timestamp, pageX, pageY, touches, changedTouches } = nativeEvent
|
|
30
30
|
const { id } = props
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { useState, ComponentType, useEffect, useCallback, useRef, ReactNode, createElement } from 'react'
|
|
2
|
+
import { View, Image, StyleSheet, Text, TouchableOpacity } from 'react-native'
|
|
3
|
+
import FastImage from '@d11/react-native-fast-image'
|
|
4
|
+
|
|
5
|
+
const asyncChunkMap = new Map()
|
|
6
|
+
|
|
7
|
+
const styles = StyleSheet.create({
|
|
8
|
+
container: {
|
|
9
|
+
flex: 1,
|
|
10
|
+
padding: 20,
|
|
11
|
+
backgroundColor: '#fff'
|
|
12
|
+
},
|
|
13
|
+
loadingImage: {
|
|
14
|
+
width: 100,
|
|
15
|
+
height: 100,
|
|
16
|
+
marginTop: 220,
|
|
17
|
+
alignSelf: 'center'
|
|
18
|
+
},
|
|
19
|
+
buttonText: {
|
|
20
|
+
color: '#fff',
|
|
21
|
+
fontSize: 16,
|
|
22
|
+
fontWeight: '500',
|
|
23
|
+
textAlign: 'center'
|
|
24
|
+
},
|
|
25
|
+
errorImage: {
|
|
26
|
+
marginTop: 80,
|
|
27
|
+
width: 220,
|
|
28
|
+
aspectRatio: 1,
|
|
29
|
+
alignSelf: 'center'
|
|
30
|
+
},
|
|
31
|
+
errorText: {
|
|
32
|
+
fontSize: 16,
|
|
33
|
+
textAlign: 'center',
|
|
34
|
+
color: '#333',
|
|
35
|
+
marginBottom: 20
|
|
36
|
+
},
|
|
37
|
+
retryButton: {
|
|
38
|
+
position: 'absolute',
|
|
39
|
+
bottom: 54,
|
|
40
|
+
left: 20,
|
|
41
|
+
right: 20,
|
|
42
|
+
backgroundColor: '#fff',
|
|
43
|
+
paddingVertical: 15,
|
|
44
|
+
borderRadius: 30,
|
|
45
|
+
marginTop: 40,
|
|
46
|
+
borderWidth: 1,
|
|
47
|
+
borderColor: '#FF5F00'
|
|
48
|
+
},
|
|
49
|
+
retryButtonText: {
|
|
50
|
+
color: '#FF5F00',
|
|
51
|
+
fontSize: 16,
|
|
52
|
+
fontWeight: '500',
|
|
53
|
+
textAlign: 'center'
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
interface DefaultFallbackProps {
|
|
58
|
+
onReload: () => void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
|
|
62
|
+
return (
|
|
63
|
+
<View style={styles.container}>
|
|
64
|
+
<Image
|
|
65
|
+
source={{
|
|
66
|
+
uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png'
|
|
67
|
+
}}
|
|
68
|
+
style={styles.errorImage}
|
|
69
|
+
resizeMode="contain"
|
|
70
|
+
/>
|
|
71
|
+
<Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
|
|
72
|
+
<TouchableOpacity
|
|
73
|
+
style={styles.retryButton}
|
|
74
|
+
onPress={onReload}
|
|
75
|
+
activeOpacity={0.7}
|
|
76
|
+
>
|
|
77
|
+
<Text style={styles.retryButtonText}>点击重试</Text>
|
|
78
|
+
</TouchableOpacity>
|
|
79
|
+
</View>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const DefaultLoading = () => {
|
|
84
|
+
return (
|
|
85
|
+
<View style={styles.container}>
|
|
86
|
+
<FastImage
|
|
87
|
+
style={styles.loadingImage}
|
|
88
|
+
source={{
|
|
89
|
+
uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif'
|
|
90
|
+
}}
|
|
91
|
+
resizeMode={FastImage.resizeMode.contain}
|
|
92
|
+
></FastImage>
|
|
93
|
+
</View>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface AsyncSuspenseProps {
|
|
98
|
+
type: 'component' | 'page'
|
|
99
|
+
chunkName: string
|
|
100
|
+
moduleId: string
|
|
101
|
+
innerProps: any,
|
|
102
|
+
loading?: ComponentType<unknown>
|
|
103
|
+
fallback?: ComponentType<unknown>
|
|
104
|
+
getChildren: () => Promise<ReactNode>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type ComponentStauts = 'pending' | 'error' | 'loaded'
|
|
108
|
+
|
|
109
|
+
const AsyncSuspense: React.FC<AsyncSuspenseProps> = ({
|
|
110
|
+
type,
|
|
111
|
+
innerProps,
|
|
112
|
+
chunkName,
|
|
113
|
+
moduleId,
|
|
114
|
+
loading,
|
|
115
|
+
fallback,
|
|
116
|
+
getChildren
|
|
117
|
+
}) => {
|
|
118
|
+
const [status, setStatus] = useState<ComponentStauts>('pending')
|
|
119
|
+
const chunkLoaded = asyncChunkMap.has(moduleId)
|
|
120
|
+
const loadChunkPromise = useRef<null | Promise<ReactNode>>(null)
|
|
121
|
+
|
|
122
|
+
const reloadPage = useCallback(() => {
|
|
123
|
+
setStatus('pending')
|
|
124
|
+
}, [])
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
let cancelled = false
|
|
128
|
+
if (!chunkLoaded && status === 'pending') {
|
|
129
|
+
if (loadChunkPromise.current) {
|
|
130
|
+
loadChunkPromise
|
|
131
|
+
.current.then((res: ReactNode) => {
|
|
132
|
+
if (cancelled) return
|
|
133
|
+
asyncChunkMap.set(moduleId, res)
|
|
134
|
+
setStatus('loaded')
|
|
135
|
+
})
|
|
136
|
+
.catch((e) => {
|
|
137
|
+
if (cancelled) return
|
|
138
|
+
if (type === 'component') {
|
|
139
|
+
global.onLazyLoadError({
|
|
140
|
+
type: 'subpackage',
|
|
141
|
+
subpackage: [chunkName],
|
|
142
|
+
errMsg: `loadSubpackage: ${e.type}`
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
loadChunkPromise.current = null
|
|
146
|
+
setStatus('error')
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return () => {
|
|
152
|
+
cancelled = true
|
|
153
|
+
}
|
|
154
|
+
}, [status])
|
|
155
|
+
|
|
156
|
+
if (chunkLoaded) {
|
|
157
|
+
const Comp = asyncChunkMap.get(moduleId)
|
|
158
|
+
return createElement(Comp, innerProps)
|
|
159
|
+
} else if (status === 'error') {
|
|
160
|
+
if (type === 'page') {
|
|
161
|
+
fallback = fallback || DefaultFallback
|
|
162
|
+
return createElement(fallback as ComponentType<DefaultFallbackProps>, { onReload: reloadPage })
|
|
163
|
+
} else {
|
|
164
|
+
return fallback ? createElement(fallback, innerProps) : null
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
if (!loadChunkPromise.current) {
|
|
168
|
+
loadChunkPromise.current = getChildren()
|
|
169
|
+
}
|
|
170
|
+
if (type === 'page') {
|
|
171
|
+
return createElement(loading || DefaultLoading)
|
|
172
|
+
} else {
|
|
173
|
+
return fallback ? createElement(fallback, innerProps) : null
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
AsyncSuspense.displayName = 'MpxAsyncSuspense'
|
|
179
|
+
|
|
180
|
+
export default AsyncSuspense
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ✘ scale-area
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { View } from 'react-native'
|
|
6
|
-
import { JSX, forwardRef, ReactNode, useRef, useMemo,
|
|
7
|
-
import { GestureDetector, Gesture } from 'react-native-gesture-handler'
|
|
8
|
-
import { useSharedValue } from 'react-native-reanimated'
|
|
6
|
+
import { JSX, forwardRef, ReactNode, useRef, useMemo, createElement } from 'react'
|
|
9
7
|
import useNodesRef, { HandlerRef } from './useNodesRef'
|
|
10
8
|
import useInnerProps from './getInnerListeners'
|
|
11
|
-
import { MovableAreaContext
|
|
9
|
+
import { MovableAreaContext } from './context'
|
|
12
10
|
import { useTransformStyle, wrapChildren, useLayout, extendObject } from './utils'
|
|
13
11
|
import Portal from './mpx-portal'
|
|
14
12
|
|
|
@@ -17,7 +15,6 @@ interface MovableAreaProps {
|
|
|
17
15
|
children: ReactNode
|
|
18
16
|
width?: number
|
|
19
17
|
height?: number
|
|
20
|
-
'scale-area'?: boolean
|
|
21
18
|
'enable-offset'?: boolean
|
|
22
19
|
'enable-var'?: boolean
|
|
23
20
|
'external-var-context'?: Record<string, any>
|
|
@@ -26,21 +23,8 @@ interface MovableAreaProps {
|
|
|
26
23
|
'parent-height'?: number
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
interface MovableViewCallbacks {
|
|
30
|
-
onScale: (scaleInfo: {scale: number}) => void
|
|
31
|
-
onScaleEnd?: () => void
|
|
32
|
-
}
|
|
33
|
-
|
|
34
26
|
const _MovableArea = forwardRef<HandlerRef<View, MovableAreaProps>, MovableAreaProps>((props: MovableAreaProps, ref): JSX.Element => {
|
|
35
|
-
const {
|
|
36
|
-
style = {},
|
|
37
|
-
'scale-area': scaleArea = false,
|
|
38
|
-
'enable-var': enableVar,
|
|
39
|
-
'external-var-context': externalVarContext,
|
|
40
|
-
'parent-font-size': parentFontSize,
|
|
41
|
-
'parent-width': parentWidth,
|
|
42
|
-
'parent-height': parentHeight
|
|
43
|
-
} = props
|
|
27
|
+
const { style = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props
|
|
44
28
|
|
|
45
29
|
const {
|
|
46
30
|
hasSelfPercent,
|
|
@@ -52,67 +36,17 @@ const _MovableArea = forwardRef<HandlerRef<View, MovableAreaProps>, MovableAreaP
|
|
|
52
36
|
setHeight
|
|
53
37
|
} = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
|
|
54
38
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
useNodesRef(props, ref, movableAreaRef, {
|
|
39
|
+
const movableViewRef = useRef(null)
|
|
40
|
+
useNodesRef(props, ref, movableViewRef, {
|
|
58
41
|
style: normalStyle
|
|
59
42
|
})
|
|
60
43
|
|
|
61
|
-
|
|
62
|
-
const registerMovableView = useCallback((id: string, callbacks: { onScale?: (scaleInfo: { scale: number }) => void; onScaleEnd?: () => void }) => {
|
|
63
|
-
movableViewsValue.value = extendObject(movableViewsValue.value, { [id]: callbacks })
|
|
64
|
-
}, [])
|
|
65
|
-
|
|
66
|
-
const unregisterMovableView = useCallback((id: string) => {
|
|
67
|
-
delete movableViewsValue.value[id]
|
|
68
|
-
}, [])
|
|
69
|
-
|
|
70
|
-
// 处理区域缩放手势
|
|
71
|
-
const handleAreaScale = useCallback((scaleInfo: { scale: number }) => {
|
|
72
|
-
'worklet'
|
|
73
|
-
if (scaleArea) {
|
|
74
|
-
// 将缩放信息广播给所有注册的 MovableView
|
|
75
|
-
Object.values(movableViewsValue.value).forEach((callbacks) => {
|
|
76
|
-
callbacks.onScale && callbacks.onScale(scaleInfo)
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
}, [scaleArea])
|
|
80
|
-
|
|
81
|
-
// 处理区域缩放结束
|
|
82
|
-
const handleAreaScaleEnd = useCallback(() => {
|
|
83
|
-
'worklet'
|
|
84
|
-
if (scaleArea) {
|
|
85
|
-
// 通知所有注册的 MovableView 缩放结束
|
|
86
|
-
Object.values(movableViewsValue.value).forEach((callbacks) => {
|
|
87
|
-
callbacks.onScaleEnd && callbacks.onScaleEnd()
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
}, [scaleArea])
|
|
91
|
-
|
|
92
|
-
const contextValue: MovableAreaContextValue = useMemo(() => ({
|
|
44
|
+
const contextValue = useMemo(() => ({
|
|
93
45
|
height: normalStyle.height || 10,
|
|
94
|
-
width: normalStyle.width || 10
|
|
95
|
-
|
|
96
|
-
registerMovableView,
|
|
97
|
-
unregisterMovableView
|
|
98
|
-
}), [normalStyle.width, normalStyle.height, scaleArea])
|
|
46
|
+
width: normalStyle.width || 10
|
|
47
|
+
}), [normalStyle.width, normalStyle.height])
|
|
99
48
|
|
|
100
|
-
const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef:
|
|
101
|
-
|
|
102
|
-
// 创建缩放手势
|
|
103
|
-
const scaleGesture = useMemo(() => {
|
|
104
|
-
if (!scaleArea) return null
|
|
105
|
-
|
|
106
|
-
return Gesture.Pinch()
|
|
107
|
-
.onUpdate((e) => {
|
|
108
|
-
'worklet'
|
|
109
|
-
handleAreaScale(e)
|
|
110
|
-
})
|
|
111
|
-
.onEnd(() => {
|
|
112
|
-
'worklet'
|
|
113
|
-
handleAreaScaleEnd()
|
|
114
|
-
})
|
|
115
|
-
}, [scaleArea])
|
|
49
|
+
const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: movableViewRef })
|
|
116
50
|
|
|
117
51
|
const innerProps = useInnerProps(
|
|
118
52
|
extendObject(
|
|
@@ -121,7 +55,7 @@ const _MovableArea = forwardRef<HandlerRef<View, MovableAreaProps>, MovableAreaP
|
|
|
121
55
|
layoutProps,
|
|
122
56
|
{
|
|
123
57
|
style: extendObject({ height: contextValue.height, width: contextValue.width }, normalStyle, layoutStyle),
|
|
124
|
-
ref:
|
|
58
|
+
ref: movableViewRef
|
|
125
59
|
}
|
|
126
60
|
),
|
|
127
61
|
[],
|
|
@@ -139,30 +73,9 @@ const _MovableArea = forwardRef<HandlerRef<View, MovableAreaProps>, MovableAreaP
|
|
|
139
73
|
}
|
|
140
74
|
)
|
|
141
75
|
))
|
|
142
|
-
|
|
143
|
-
// 如果启用了 scale-area,包装一个 GestureDetector
|
|
144
|
-
if (scaleArea && scaleGesture) {
|
|
145
|
-
movableComponent = createElement(MovableAreaContext.Provider, { value: contextValue }, createElement(
|
|
146
|
-
GestureDetector,
|
|
147
|
-
{ gesture: scaleGesture },
|
|
148
|
-
createElement(
|
|
149
|
-
View,
|
|
150
|
-
innerProps,
|
|
151
|
-
wrapChildren(
|
|
152
|
-
props,
|
|
153
|
-
{
|
|
154
|
-
hasVarDec,
|
|
155
|
-
varContext: varContextRef.current
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
)
|
|
159
|
-
))
|
|
160
|
-
}
|
|
161
|
-
|
|
162
76
|
if (hasPositionFixed) {
|
|
163
77
|
movableComponent = createElement(Portal, null, movableComponent)
|
|
164
78
|
}
|
|
165
|
-
|
|
166
79
|
return movableComponent
|
|
167
80
|
})
|
|
168
81
|
|