@mpxjs/webpack-plugin 2.10.11-beta.1 → 2.10.12

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.
@@ -1,392 +0,0 @@
1
- import React, { createElement, forwardRef, useCallback, useEffect, useRef, useState } from 'react'
2
- import { StyleSheet, Dimensions, TouchableWithoutFeedback, StyleProp, ViewStyle } from 'react-native'
3
- import Animated, { useSharedValue, useAnimatedStyle, withTiming, cancelAnimation, runOnJS } from 'react-native-reanimated'
4
- import { GestureDetector, Gesture } from 'react-native-gesture-handler'
5
- import Portal from './mpx-portal/index'
6
- import { PreventRemoveEvent, usePreventRemove } from '@react-navigation/native'
7
- import { extendObject, useLayout, useNavigation } from './utils'
8
- import useInnerProps, { getCustomEvent } from './getInnerListeners'
9
- import useNodesRef from './useNodesRef'
10
-
11
- type Position = 'top' | 'bottom' | 'right' | 'center';
12
-
13
- interface PageContainerProps {
14
- show: boolean;
15
- duration?: number;
16
- 'z-index'?: number;
17
- overlay?: boolean;
18
- position?: Position;
19
- round?: boolean;
20
- 'close-on-slide-down'?: boolean;
21
- 'overlay-style'?: StyleProp<ViewStyle>;
22
- 'custom-style'?: StyleProp<ViewStyle>;
23
- bindbeforeenter?: (event: CustomEvent) => void;
24
- bindenter?: (event: CustomEvent) => void;
25
- bindafterenter?: (event: CustomEvent) => void;
26
- bindbeforeleave?: (event: CustomEvent) => void;
27
- bindleave?: (event: CustomEvent) => void;
28
- bindafterleave?: (event: CustomEvent) => void;
29
- bindclickoverlay?: (event: CustomEvent) => void;
30
-
31
- bindclose: (event: CustomEvent<{ value: boolean}>) => void;
32
- children: React.ReactNode;
33
- }
34
-
35
- const screenWidth = Dimensions.get('screen').width
36
-
37
- function nextTick (cb: () => void) {
38
- setTimeout(cb, 0)
39
- }
40
-
41
- function getInitialTranslate (position: Position) {
42
- switch (position) {
43
- case 'top':
44
- return -100
45
- case 'bottom':
46
- return 100
47
- case 'right':
48
- return 100
49
- case 'center':
50
- return 0
51
- }
52
- }
53
- function getRoundStyle (position: Position) {
54
- switch (position) {
55
- case 'top':
56
- return {
57
- borderBottomLeftRadius: 20,
58
- borderBottomRightRadius: 20
59
- }
60
- case 'bottom':
61
- return {
62
- borderTopLeftRadius: 20,
63
- borderTopRightRadius: 20
64
- }
65
- default: return {}
66
- }
67
- }
68
-
69
- const PageContainer = forwardRef<any, PageContainerProps>((props, ref) => {
70
- const {
71
- show,
72
- duration = 300,
73
- 'z-index': zIndex = 100,
74
- overlay = true,
75
- position = 'bottom',
76
- round = false,
77
- 'close-on-slide-down': closeOnSlideDown = false,
78
- 'overlay-style': overlayStyle,
79
- 'custom-style': customStyle,
80
- bindclose, // RN下特有属性,用于同步show状态到父组件
81
- bindbeforeenter,
82
- bindenter,
83
- bindafterenter,
84
- bindbeforeleave,
85
- bindleave,
86
- bindafterleave,
87
- bindclickoverlay,
88
- children
89
- } = props
90
-
91
- const isFirstRenderFlag = useRef(true)
92
- const isFirstRender = isFirstRenderFlag.current
93
- if (isFirstRenderFlag.current) {
94
- isFirstRenderFlag.current = false
95
- }
96
-
97
- const triggerBeforeEnterEvent = () => bindbeforeenter?.(getCustomEvent('beforeenter', {}, {}, props))
98
- const triggerEnterEvent = () => bindenter?.(getCustomEvent('enter', {}, {}, props))
99
- const triggerAfterEnterEvent = () => bindafterenter?.(getCustomEvent('afterenter', {}, {}, props))
100
- const triggerBeforeLeaveEvent = () => bindbeforeleave?.(getCustomEvent('beforeleave', {}, {}, props))
101
- const triggerLeaveEvent = () => bindleave?.(getCustomEvent('leave', {}, {}, props))
102
- const triggerAfterLeaveEvent = () => bindafterleave?.(getCustomEvent('afterleave', {}, {}, props))
103
-
104
- const close = () => bindclose(getCustomEvent('close', {}, { detail: { value: false, source: 'close' } }, props))
105
-
106
- // 控制组件是否挂载
107
- const [internalVisible, setInternalVisible] = useState(show)
108
-
109
- const overlayOpacity = useSharedValue(0)
110
- const contentOpacity = useSharedValue(position === 'center' ? 0 : 1)
111
- const contentTranslate = useSharedValue(getInitialTranslate(position))
112
-
113
- const overlayAnimatedStyle = useAnimatedStyle(() => ({
114
- opacity: overlayOpacity.value
115
- }))
116
-
117
- const sharedPosition = useSharedValue(position)
118
- useEffect(() => {
119
- sharedPosition.set(position)
120
- }, [position])
121
- const contentAnimatedStyle = useAnimatedStyle(() => {
122
- let transform: ViewStyle['transform'] = []
123
- if (sharedPosition.value === 'top' || sharedPosition.value === 'bottom') {
124
- transform = [{ translateY: `${contentTranslate.value}%` }]
125
- } else if (sharedPosition.value === 'right') {
126
- transform = [{ translateX: `${contentTranslate.value}%` }]
127
- }
128
-
129
- return {
130
- opacity: contentOpacity.value,
131
- transform
132
- }
133
- })
134
-
135
- const currentTick = useRef(0)
136
- function createTick () {
137
- currentTick.current++
138
- const current = currentTick.current
139
- console.log('createTick ', current)
140
- return () => {
141
- console.log('isCurrentTick ', current, ', global tick is ' + currentTick.current)
142
- return currentTick.current === current
143
- }
144
- }
145
-
146
- function clearAnimation () {
147
- cancelAnimation(overlayOpacity)
148
- cancelAnimation(contentOpacity)
149
- cancelAnimation(contentTranslate)
150
- }
151
-
152
- // 播放入场动画
153
- const animateIn = () => {
154
- const isCurrentTick = createTick()
155
- triggerBeforeEnterEvent()
156
- nextTick(() => {
157
- const animateOutFinish = internalVisible === false
158
- setInternalVisible(true)
159
- triggerEnterEvent()
160
- if (!isCurrentTick()) return
161
-
162
- const animateEnd = () => {
163
- if (!isCurrentTick()) return
164
- triggerAfterEnterEvent()
165
- }
166
- /**
167
- * 对齐 微信小程序
168
- * 如果退场动画已经结束,则需将内容先移动到对应动画的初始位置
169
- * 否则,结束退场动画,从当前位置作为初始位置完成进场动画,且退场动画时长将缩短
170
- */
171
- let durationTime = duration
172
- if (animateOutFinish) {
173
- contentTranslate.set(getInitialTranslate(position))
174
- contentOpacity.set(position === 'center' ? 0 : 1)
175
- } else {
176
- clearAnimation()
177
- if (position === 'center') {
178
- durationTime = durationTime * (1 - contentOpacity.value)
179
- } else {
180
- durationTime = durationTime * (contentTranslate.value / 100)
181
- }
182
- }
183
-
184
- overlayOpacity.value = withTiming(1, { duration: durationTime })
185
- if (position === 'center') {
186
- contentOpacity.value = withTiming(1, { duration: durationTime }, () => {
187
- runOnJS(animateEnd)()
188
- })
189
- contentTranslate.value = withTiming(0, { duration: durationTime })
190
- } else {
191
- contentOpacity.value = withTiming(1, { duration: durationTime })
192
- contentTranslate.value = withTiming(0, { duration: durationTime }, () => {
193
- runOnJS(animateEnd)()
194
- })
195
- }
196
- })
197
- }
198
-
199
- // 播放离场动画
200
- const animateOut = () => {
201
- const isCurrentTick = createTick()
202
- triggerBeforeLeaveEvent()
203
- nextTick(() => {
204
- triggerLeaveEvent()
205
- if (!isCurrentTick()) return
206
-
207
- const animateEnd = () => {
208
- if (!isCurrentTick()) return // 如果动画被cancelAnimation依然会触发回调,所以在此也需要判断Tick
209
- triggerAfterLeaveEvent()
210
- setInternalVisible(false)
211
- }
212
-
213
- if (position === 'center') {
214
- contentOpacity.value = withTiming(0, { duration }, () => {
215
- runOnJS(animateEnd)()
216
- })
217
- contentTranslate.value = withTiming(getInitialTranslate(position), { duration })
218
- } else {
219
- contentOpacity.value = withTiming(0, { duration })
220
- contentTranslate.value = withTiming(getInitialTranslate(position), { duration }, () => {
221
- runOnJS(animateEnd)()
222
- })
223
- }
224
- overlayOpacity.value = withTiming(0, { duration })
225
- })
226
- }
227
-
228
- useEffect(() => {
229
- // 如果展示状态和挂载状态一致,则不需要做任何操作
230
- if (show) {
231
- animateIn()
232
- } else {
233
- if (!isFirstRender) animateOut()
234
- }
235
- }, [show])
236
-
237
- const navigation = useNavigation()
238
- usePreventRemove(show, (event: PreventRemoveEvent) => {
239
- const { data } = event
240
- if (show) {
241
- close()
242
- } else {
243
- navigation?.dispatch(data.action)
244
- }
245
- })
246
-
247
- /**
248
- * IOS 下需要关闭手势返回(原因: IOS手势返回时页面会跟随手指滑动,但是实际返回动作是在松手时触发,需禁掉页面跟随手指滑动的效果)
249
- * 禁用与启用逻辑抽离为rnConfig由外部实现,并补充纯RN下默认实现
250
- */
251
-
252
- if (__mpx_mode__ === 'ios') {
253
- // eslint-disable-next-line react-hooks/rules-of-hooks
254
- useEffect(() => {
255
- if (typeof global.__mpx.config?.rnConfig?.disableSwipeBack === 'function') {
256
- global.__mpx.config.rnConfig.disableSwipeBack({ disable: show })
257
- } else {
258
- navigation?.setOptions({
259
- gestureEnabled: !show
260
- })
261
- }
262
- }, [show])
263
- }
264
-
265
- const THRESHOLD = screenWidth * 0.3 // 距离阈值
266
- const VELOCITY_THRESHOLD = 1000 // px/s
267
- const contentGesture = Gesture.Pan()
268
- .activeOffsetY(150) // 下滑至少滑动 150px 才处理
269
- .onEnd((e) => {
270
- const { velocityY, translationY } = e
271
- const shouldGoBack = translationY > THRESHOLD || velocityY > VELOCITY_THRESHOLD
272
- if (shouldGoBack) {
273
- runOnJS(close)()
274
- }
275
- })
276
- /**
277
- * 全屏幕 IOS 左滑手势返回(ios默认页面返回存在页面跟手逻辑,page-container暂不支持,对齐微信小程序)
278
- * 1: 仅在屏幕左边缘滑动 才触发返回手势。
279
- * 2: 用户滑动距离足够 或 滑动速度足够快,才触发返回。
280
- * 3: 用户中途回退(滑回来)或滑太慢/太短,则应取消返回。
281
- */
282
- const screenGesture = Gesture.Pan()
283
- .onEnd((e) => {
284
- const { velocityX, translationX } = e
285
- const shouldGoBack = translationX > THRESHOLD || velocityX > VELOCITY_THRESHOLD
286
- if (shouldGoBack) {
287
- runOnJS(close)()
288
- }
289
- })
290
- .hitSlop({ left: 0, width: 30 }) // 从屏幕左侧 30px 内触发才处理
291
-
292
- const renderMask = () => {
293
- const onPress = () => {
294
- close()
295
- bindclickoverlay?.(getCustomEvent(
296
- 'clickoverlay',
297
- {},
298
- { detail: { value: false, source: 'clickoverlay' } },
299
- props
300
- ))
301
- }
302
- return createElement(TouchableWithoutFeedback, { onPress },
303
- createElement(Animated.View, { style: [styles.overlay, overlayStyle, overlayAnimatedStyle] }))
304
- }
305
-
306
- const renderContent = () => {
307
- const contentView = (
308
- <Animated.View style={[
309
- styles.container,
310
- getRoundStyle(position),
311
- positionStyle[position],
312
- customStyle,
313
- contentAnimatedStyle
314
- ]}>
315
- {children}
316
- </Animated.View>
317
- )
318
-
319
- return closeOnSlideDown
320
- ? <GestureDetector gesture={contentGesture}>{contentView}</GestureDetector>
321
- : contentView
322
- }
323
-
324
- const nodeRef = useRef(null)
325
- useNodesRef(props, ref, nodeRef, {})
326
- const { layoutRef, layoutProps } = useLayout({ props, hasSelfPercent: false, nodeRef })
327
- const innerProps = useInnerProps(
328
- extendObject(
329
- {},
330
- props,
331
- {
332
- ref: nodeRef
333
- },
334
- layoutProps
335
- ),
336
- [],
337
- { layoutRef }
338
- )
339
- const wrapperProps = extendObject(
340
- innerProps,
341
- {
342
- style: [styles.wrapper, { zIndex }]
343
- }
344
- )
345
-
346
- const renderWrapped = () => {
347
- const wrappedView = (
348
- <Animated.View {...wrapperProps}>
349
- {overlay ? renderMask() : null}
350
- {renderContent()}
351
- </Animated.View>
352
- )
353
- return __mpx_mode__ === 'ios'
354
- ? <GestureDetector gesture={screenGesture}>{wrappedView}</GestureDetector>
355
- : wrappedView
356
- }
357
-
358
- // TODO 是否有必要支持refs? dataset?
359
- return createElement(Portal, null, internalVisible ? renderWrapped() : null)
360
- })
361
-
362
- const styles = StyleSheet.create({
363
- wrapper: extendObject(
364
- {
365
- justifyContent: 'flex-end',
366
- alignItems: 'center'
367
- } as const,
368
- StyleSheet.absoluteFillObject
369
- ),
370
- overlay: extendObject(
371
- {
372
- backgroundColor: 'rgba(0,0,0,0.5)'
373
- },
374
- StyleSheet.absoluteFillObject
375
- ),
376
- container: {
377
- position: 'absolute',
378
- backgroundColor: 'white',
379
- overflow: 'hidden'
380
- }
381
- })
382
-
383
- const positionStyle: Record<Position, ViewStyle> = {
384
- bottom: { bottom: 0, width: '100%', height: 'auto' },
385
- top: { top: 0, width: '100%', height: 'auto' },
386
- right: extendObject({}, StyleSheet.absoluteFillObject, { right: 0 }),
387
- center: extendObject({}, StyleSheet.absoluteFillObject)
388
- }
389
-
390
- PageContainer.displayName = 'PageContainer'
391
-
392
- export default PageContainer