@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.
Files changed (50) hide show
  1. package/lib/dependencies/RecordPageConfigsMapDependency.js +1 -1
  2. package/lib/dependencies/RequireExternalDependency.js +61 -0
  3. package/lib/file-loader.js +3 -2
  4. package/lib/index.js +55 -9
  5. package/lib/json-compiler/index.js +1 -0
  6. package/lib/parser.js +1 -1
  7. package/lib/platform/json/wx/index.js +43 -25
  8. package/lib/platform/style/wx/index.js +7 -0
  9. package/lib/platform/template/wx/component-config/fix-component-name.js +2 -2
  10. package/lib/platform/template/wx/component-config/index.js +7 -1
  11. package/lib/platform/template/wx/component-config/page-container.js +19 -0
  12. package/lib/platform/template/wx/component-config/sticky-header.js +23 -0
  13. package/lib/platform/template/wx/component-config/sticky-section.js +23 -0
  14. package/lib/platform/template/wx/component-config/unsupported.js +1 -1
  15. package/lib/react/LoadAsyncChunkModule.js +74 -0
  16. package/lib/react/index.js +3 -1
  17. package/lib/react/processJSON.js +74 -13
  18. package/lib/react/processScript.js +6 -6
  19. package/lib/react/script-helper.js +100 -41
  20. package/lib/runtime/components/react/context.ts +12 -3
  21. package/lib/runtime/components/react/dist/context.js +4 -1
  22. package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +135 -0
  23. package/lib/runtime/components/react/dist/mpx-button.jsx +2 -2
  24. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +8 -6
  25. package/lib/runtime/components/react/dist/mpx-page-container.jsx +255 -0
  26. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +31 -15
  27. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +117 -0
  28. package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +45 -0
  29. package/lib/runtime/components/react/dist/mpx-swiper.jsx +3 -2
  30. package/lib/runtime/components/react/mpx-async-suspense.tsx +180 -0
  31. package/lib/runtime/components/react/mpx-button.tsx +3 -2
  32. package/lib/runtime/components/react/mpx-movable-view.tsx +8 -4
  33. package/lib/runtime/components/react/mpx-page-container.tsx +394 -0
  34. package/lib/runtime/components/react/mpx-scroll-view.tsx +84 -59
  35. package/lib/runtime/components/react/mpx-sticky-header.tsx +181 -0
  36. package/lib/runtime/components/react/mpx-sticky-section.tsx +96 -0
  37. package/lib/runtime/components/react/mpx-swiper.tsx +4 -2
  38. package/lib/runtime/components/web/mpx-scroll-view.vue +18 -4
  39. package/lib/runtime/components/web/mpx-sticky-header.vue +99 -0
  40. package/lib/runtime/components/web/mpx-sticky-section.vue +15 -0
  41. package/lib/runtime/optionProcessorReact.d.ts +18 -0
  42. package/lib/runtime/optionProcessorReact.js +30 -0
  43. package/lib/script-setup-compiler/index.js +27 -5
  44. package/lib/template-compiler/bind-this.js +2 -1
  45. package/lib/template-compiler/compiler.js +27 -6
  46. package/lib/utils/dom-tag-config.js +18 -4
  47. package/lib/utils/trans-async-sub-rules.js +19 -0
  48. package/lib/web/script-helper.js +1 -1
  49. package/package.json +4 -4
  50. package/LICENSE +0 -433
@@ -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
@@ -42,7 +42,8 @@ import {
42
42
  TextStyle,
43
43
  Animated,
44
44
  Easing,
45
- NativeSyntheticEvent
45
+ NativeSyntheticEvent,
46
+ useAnimatedValue
46
47
  } from 'react-native'
47
48
  import { warn } from '@mpxjs/utils'
48
49
  import { GestureDetector, PanGesture } from 'react-native-gesture-handler'
@@ -157,7 +158,7 @@ const timer = (data: any, time = 3000) => new Promise((resolve) => {
157
158
  })
158
159
 
159
160
  const Loading = ({ alone = false }: { alone: boolean }): JSX.Element => {
160
- const image = useRef(new Animated.Value(0)).current
161
+ const image = useAnimatedValue(0)
161
162
 
162
163
  const rotate = image.interpolate({
163
164
  inputRange: [0, 1],
@@ -70,6 +70,7 @@ interface MovableViewProps {
70
70
  'parent-font-size'?: number
71
71
  'parent-width'?: number
72
72
  'parent-height'?: number
73
+ 'disable-event-passthrough'?: boolean
73
74
  }
74
75
 
75
76
  const styles = StyleSheet.create({
@@ -103,6 +104,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
103
104
  'parent-width': parentWidth,
104
105
  'parent-height': parentHeight,
105
106
  direction = 'none',
107
+ 'disable-event-passthrough': disableEventPassthrough = false,
106
108
  'simultaneous-handlers': originSimultaneousHandlers = [],
107
109
  'wait-for': waitFor = [],
108
110
  style = {},
@@ -557,10 +559,12 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
557
559
  })
558
560
  .withRef(movableGestureRef)
559
561
 
560
- if (direction === 'horizontal') {
561
- gesturePan.activeOffsetX([-5, 5]).failOffsetY([-5, 5])
562
- } else if (direction === 'vertical') {
563
- gesturePan.activeOffsetY([-5, 5]).failOffsetX([-5, 5])
562
+ if (!disableEventPassthrough) {
563
+ if (direction === 'horizontal') {
564
+ gesturePan.activeOffsetX([-5, 5]).failOffsetY([-5, 5])
565
+ } else if (direction === 'vertical') {
566
+ gesturePan.activeOffsetY([-5, 5]).failOffsetX([-5, 5])
567
+ }
564
568
  }
565
569
 
566
570
  if (simultaneousHandlers && simultaneousHandlers.length) {
@@ -0,0 +1,394 @@
1
+ import React, { createElement, forwardRef, useEffect, useRef, useState } from 'react'
2
+ import {
3
+ StyleSheet,
4
+ Animated,
5
+ Dimensions,
6
+ TouchableWithoutFeedback,
7
+ PanResponder,
8
+ StyleProp,
9
+ ViewStyle
10
+ } from 'react-native'
11
+ import Portal from './mpx-portal/index'
12
+ import { PreventRemoveEvent, usePreventRemove } from '@react-navigation/native'
13
+ import { extendObject, useLayout, useNavigation } from './utils'
14
+ import useInnerProps, { getCustomEvent } from './getInnerListeners'
15
+ import useNodesRef from './useNodesRef'
16
+
17
+ type Position = 'top' | 'bottom' | 'right' | 'center';
18
+
19
+ interface PageContainerProps {
20
+ show: boolean;
21
+ duration?: number;
22
+ 'z-index'?: number;
23
+ overlay?: boolean;
24
+ position?: Position;
25
+ round?: boolean;
26
+ 'close-on-slide-down'?: boolean;
27
+ 'overlay-style'?: StyleProp<ViewStyle>;
28
+ 'custom-style'?: StyleProp<ViewStyle>;
29
+
30
+ bindclose: (event: CustomEvent<{ value: boolean}>) => void;
31
+
32
+ bindbeforeenter?: (event: CustomEvent) => void;
33
+ bindenter?: (event: CustomEvent) => void;
34
+ bindafterenter?: (event: CustomEvent) => void;
35
+ bindbeforeleave?: (event: CustomEvent) => void;
36
+ bindleave?: (event: CustomEvent) => void;
37
+ bindafterleave?: (event: CustomEvent) => void;
38
+ bindclickoverlay?: (event: CustomEvent) => void;
39
+ children: React.ReactNode;
40
+ }
41
+
42
+ const screenHeight = Dimensions.get('window').height
43
+ const screenWidth = Dimensions.get('window').width
44
+
45
+ function nextTick (cb: () => void) {
46
+ setTimeout(cb, 0)
47
+ }
48
+
49
+ export default forwardRef<any, PageContainerProps>((props, ref) => {
50
+ const {
51
+ show,
52
+ duration = 300,
53
+ 'z-index': zIndex = 100,
54
+ overlay = true,
55
+ position = 'bottom',
56
+ round = false,
57
+ 'close-on-slide-down': closeOnSlideDown = false,
58
+ 'overlay-style': overlayStyle,
59
+ 'custom-style': customStyle,
60
+ bindclose, // RN下特有属性,用于同步show状态到父组件
61
+ bindbeforeenter,
62
+ bindenter,
63
+ bindafterenter,
64
+ bindbeforeleave,
65
+ bindleave,
66
+ bindafterleave,
67
+ bindclickoverlay,
68
+ children
69
+ } = props
70
+
71
+ const isFirstRenderFlag = useRef(true)
72
+ const isFirstRender = isFirstRenderFlag.current
73
+ if (isFirstRenderFlag.current) {
74
+ isFirstRenderFlag.current = false
75
+ }
76
+
77
+ const close = () => {
78
+ bindclose(getCustomEvent(
79
+ 'close',
80
+ {},
81
+ { detail: { value: false, source: 'close' } },
82
+ props
83
+ ))
84
+ }
85
+
86
+ const [internalVisible, setInternalVisible] = useState(show) // 控制组件是否挂载
87
+
88
+ const overlayOpacity = useRef(new Animated.Value(0)).current
89
+ const contentOpacity = useRef(new Animated.Value(position === 'center' ? 0 : 1)).current
90
+ const contentTranslate = useRef(new Animated.Value(getInitialPosition())).current
91
+
92
+ const currentAnimation = useRef<Array<Animated.CompositeAnimation> | null>(null)
93
+
94
+ function getInitialPosition () {
95
+ switch (position) {
96
+ case 'top':
97
+ return -screenHeight
98
+ case 'bottom':
99
+ return screenHeight
100
+ case 'right':
101
+ return screenWidth
102
+ case 'center':
103
+ return 0
104
+ default:
105
+ return screenHeight
106
+ }
107
+ }
108
+
109
+ const currentTick = useRef(0)
110
+ function createTick () {
111
+ currentTick.current++
112
+ console.log('currentTick.current++', currentTick.current)
113
+ const current = currentTick.current
114
+ return () => {
115
+ console.log('currentTick.current', currentTick.current, 'current', current)
116
+ return currentTick.current === current
117
+ }
118
+ }
119
+ // 播放入场动画
120
+ const animateIn = () => {
121
+ const isCurrentTick = createTick()
122
+ const animateOutFinish = currentAnimation.current === null
123
+ if (!animateOutFinish) {
124
+ currentAnimation.current!.forEach((animation) => animation.stop())
125
+ }
126
+ const animations: Animated.CompositeAnimation[] = [
127
+ Animated.timing(contentTranslate, {
128
+ toValue: 0,
129
+ duration,
130
+ useNativeDriver: true
131
+ }),
132
+ Animated.timing(contentOpacity, {
133
+ toValue: 1,
134
+ duration,
135
+ useNativeDriver: true
136
+ }),
137
+ Animated.timing(overlayOpacity, {
138
+ toValue: 1,
139
+ duration,
140
+ useNativeDriver: true
141
+ })
142
+ ]
143
+ currentAnimation.current = animations
144
+ // 所有生命周期需相隔一个nextTick以保证在生命周期中修改show可在组件内部监听到
145
+ bindbeforeenter && bindbeforeenter(getCustomEvent(
146
+ 'beforeenter',
147
+ {},
148
+ { detail: { value: false, source: 'beforeenter' } },
149
+ props
150
+ ))
151
+ nextTick(() => {
152
+ bindenter && bindenter(getCustomEvent(
153
+ 'enter',
154
+ {},
155
+ { detail: { value: false, source: 'enter' } },
156
+ props
157
+ ))
158
+ // 与微信对其, bindenter 需要执行,所以 isCurrentTick 放在后面
159
+ if (!isCurrentTick()) return
160
+
161
+ console.log('animateIn start')
162
+ // 设置为动画初始状态(特殊情况, 如果退场动画没有结束 或者 退场动画还未开始,则无需初始化,而是从当前位置完成动画)
163
+ if (animateOutFinish) {
164
+ contentTranslate.setValue(getInitialPosition())
165
+ contentOpacity.setValue(position === 'center' ? 0 : 1)
166
+ }
167
+ Animated.parallel(animations).start(() => {
168
+ bindafterenter && bindafterenter(getCustomEvent(
169
+ 'afterenter',
170
+ {},
171
+ { detail: { value: false, source: 'afterenter' } },
172
+ props
173
+ ))
174
+ })
175
+ })
176
+ }
177
+
178
+ // 播放离场动画
179
+ const animateOut = () => {
180
+ const isCurrentTick = createTick()
181
+ // 停止入场动画
182
+ currentAnimation.current?.forEach((animation) => animation.stop())
183
+ const animations: Animated.CompositeAnimation[] = [Animated.timing(overlayOpacity, {
184
+ toValue: 0,
185
+ duration,
186
+ useNativeDriver: true
187
+ })
188
+ ]
189
+ if (position === 'center') {
190
+ animations.push(Animated.timing(contentOpacity, {
191
+ toValue: 0,
192
+ duration,
193
+ useNativeDriver: true
194
+ }))
195
+ } else {
196
+ animations.push(Animated.timing(contentTranslate, {
197
+ toValue: getInitialPosition(),
198
+ duration,
199
+ useNativeDriver: true
200
+ }))
201
+ }
202
+ currentAnimation.current = animations
203
+ bindbeforeleave && bindbeforeleave(getCustomEvent(
204
+ 'beforeleave',
205
+ {},
206
+ { detail: { value: false, source: 'beforeleave' } },
207
+ props
208
+ ))
209
+ nextTick(() => {
210
+ bindleave && bindleave(getCustomEvent(
211
+ 'leave',
212
+ {},
213
+ { detail: { value: false, source: 'leave' } },
214
+ props
215
+ ))
216
+ if (!isCurrentTick()) return
217
+ console.log('animateOut start')
218
+ Animated.parallel(animations).start(() => {
219
+ currentAnimation.current = null
220
+ bindafterleave && bindafterleave(getCustomEvent(
221
+ 'afterleave',
222
+ {},
223
+ { detail: { value: false, source: 'afterleave' } },
224
+ props
225
+ ))
226
+ setInternalVisible(false) // 动画播放完后,才卸载
227
+ })
228
+ })
229
+ }
230
+
231
+ useEffect(() => {
232
+ console.log('====comp show', show, 'internalVisible', internalVisible)
233
+ // 如果展示状态和挂载状态一致,则不需要做任何操作
234
+ if (show) {
235
+ setInternalVisible(true) // 确保挂载
236
+ animateIn()
237
+ } else {
238
+ if (!isFirstRender) animateOut()
239
+ }
240
+ }, [show])
241
+
242
+ const navigation = useNavigation()
243
+ usePreventRemove(show, (event: PreventRemoveEvent) => {
244
+ const { data } = event
245
+ if (show) {
246
+ close()
247
+ } else {
248
+ navigation?.dispatch(data.action)
249
+ }
250
+ })
251
+
252
+ // IOS 下需要关闭手势返回(原因: IOS手势返回时页面会跟随手指滑动,但是实际返回动作是在松手时触发,需禁掉页面跟随手指滑动的效果)
253
+ useEffect(() => {
254
+ navigation?.setOptions({
255
+ gestureEnabled: !show
256
+ })
257
+ }, [show])
258
+
259
+ const SCREEN_EDGE_THRESHOLD = 60 // 从屏幕左侧 30px 内触发
260
+
261
+ // 内容区 手势下滑关闭
262
+ const contentPanResponder = PanResponder.create({
263
+ onMoveShouldSetPanResponder: (_, gestureState) => {
264
+ const { dx, dy } = gestureState
265
+ return dy > 200 && Math.abs(dx) < 60
266
+ },
267
+ onPanResponderRelease: () => {
268
+ close()
269
+ }
270
+ })
271
+
272
+ // 全屏幕 IOS 右滑手势返回
273
+ const screenPanResponder = PanResponder.create({
274
+ onMoveShouldSetPanResponder: (_, gestureState) => {
275
+ const { moveX, dx, dy } = gestureState
276
+
277
+ const isFromEdge = moveX < SCREEN_EDGE_THRESHOLD
278
+ const isHorizontalSwipe = dx > 10 && Math.abs(dy) < 20
279
+ return isFromEdge && isHorizontalSwipe
280
+ },
281
+ onPanResponderRelease: (_, gestureState) => {
282
+ if (gestureState.dx > 100) {
283
+ close()
284
+ }
285
+ }
286
+ })
287
+
288
+ const getTransformStyle: () => ViewStyle = () => {
289
+ switch (position) {
290
+ case 'top':
291
+ case 'bottom':
292
+ return { transform: [{ translateY: contentTranslate }] }
293
+ case 'right':
294
+ return { transform: [{ translateX: contentTranslate }] }
295
+ case 'center':
296
+ return {}
297
+ }
298
+ }
299
+
300
+ const renderMask = () => {
301
+ const onPress = () => {
302
+ close()
303
+ bindclickoverlay && bindclickoverlay(getCustomEvent(
304
+ 'clickoverlay',
305
+ {},
306
+ { detail: { value: false, source: 'clickoverlay' } },
307
+ props
308
+ ))
309
+ }
310
+ return createElement(TouchableWithoutFeedback, { onPress },
311
+ createElement(Animated.View, { style: [styles.overlay, overlayStyle, { opacity: overlayOpacity }] }))
312
+ }
313
+
314
+ const renderContent = (children: React.ReactNode) => {
315
+ const contentProps = extendObject(
316
+ {
317
+ style: [
318
+ styles.container,
319
+ round ? styles.rounded : null,
320
+ positionStyle[position],
321
+ customStyle,
322
+ getTransformStyle(),
323
+ { opacity: contentOpacity }
324
+ ]
325
+ },
326
+ closeOnSlideDown ? contentPanResponder.panHandlers : null
327
+ )
328
+ return createElement(Animated.View, contentProps, children)
329
+ }
330
+
331
+ const nodeRef = useRef(null)
332
+ useNodesRef(props, ref, nodeRef, {})
333
+ const { layoutRef, layoutProps } = useLayout({ props, hasSelfPercent: false, nodeRef })
334
+ const innerProps = useInnerProps(
335
+ extendObject(
336
+ {},
337
+ props,
338
+ {
339
+ ref: nodeRef
340
+ },
341
+ layoutProps
342
+ ),
343
+ [],
344
+ { layoutRef }
345
+ )
346
+ const wrapperProps = extendObject(
347
+ innerProps,
348
+ {
349
+ style: [styles.wrapper, { zIndex }]
350
+ },
351
+ __mpx_mode__ === 'ios' ? screenPanResponder.panHandlers : {}
352
+ )
353
+
354
+ // TODO 是否有必要支持refs? dataset?
355
+ return createElement(Portal, null,
356
+ internalVisible
357
+ ? createElement(Animated.View, wrapperProps,
358
+ overlay ? renderMask() : null,
359
+ renderContent(children)
360
+ )
361
+ : null
362
+ )
363
+ })
364
+
365
+ const styles = StyleSheet.create({
366
+ wrapper: extendObject(
367
+ {
368
+ justifyContent: 'flex-end',
369
+ alignItems: 'center'
370
+ } as const,
371
+ StyleSheet.absoluteFillObject
372
+ ),
373
+ overlay: extendObject(
374
+ {
375
+ backgroundColor: 'rgba(0,0,0,0.5)'
376
+ },
377
+ StyleSheet.absoluteFillObject
378
+ ),
379
+ container: {
380
+ position: 'absolute',
381
+ backgroundColor: 'white'
382
+ },
383
+ rounded: {
384
+ borderTopLeftRadius: 20,
385
+ borderTopRightRadius: 20
386
+ }
387
+ })
388
+
389
+ const positionStyle: Record<Position, ViewStyle> = {
390
+ bottom: { bottom: 0, width: '100%', height: 'auto' },
391
+ top: { top: 0, width: '100%', height: 'auto' },
392
+ right: extendObject({}, StyleSheet.absoluteFillObject, { right: 0 }),
393
+ center: extendObject({}, StyleSheet.absoluteFillObject)
394
+ }