@mpxjs/webpack-plugin 2.10.14-beta.2 → 2.10.14-beta.21

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 (106) hide show
  1. package/lib/dependencies/ImportDependency.js +102 -0
  2. package/lib/index.js +18 -11
  3. package/lib/platform/style/wx/index.js +7 -1
  4. package/lib/platform/template/wx/component-config/button.js +13 -4
  5. package/lib/platform/template/wx/component-config/index.js +3 -1
  6. package/lib/platform/template/wx/component-config/nav-container.js +27 -0
  7. package/lib/react/LoadAsyncChunkModule.js +2 -5
  8. package/lib/runtime/components/ali/mpx-nav-container.mpx +3 -0
  9. package/lib/runtime/components/react/context.ts +18 -6
  10. package/lib/runtime/components/react/dist/context.d.ts +79 -0
  11. package/lib/runtime/components/react/dist/context.js +1 -0
  12. package/lib/runtime/components/react/dist/event.config.d.ts +7 -0
  13. package/lib/runtime/components/react/dist/getInnerListeners.d.ts +7 -0
  14. package/lib/runtime/components/react/dist/mpx-async-suspense.d.ts +12 -0
  15. package/lib/runtime/components/react/dist/mpx-button.d.ts +68 -0
  16. package/lib/runtime/components/react/dist/mpx-canvas/Bus.d.ts +23 -0
  17. package/lib/runtime/components/react/dist/mpx-canvas/CanvasGradient.d.ts +7 -0
  18. package/lib/runtime/components/react/dist/mpx-canvas/CanvasRenderingContext2D.d.ts +6 -0
  19. package/lib/runtime/components/react/dist/mpx-canvas/Image.d.ts +20 -0
  20. package/lib/runtime/components/react/dist/mpx-canvas/ImageData.d.ts +8 -0
  21. package/lib/runtime/components/react/dist/mpx-canvas/constructorsRegistry.d.ts +10 -0
  22. package/lib/runtime/components/react/dist/mpx-canvas/html.d.ts +2 -0
  23. package/lib/runtime/components/react/dist/mpx-canvas/index.d.ts +32 -0
  24. package/lib/runtime/components/react/dist/mpx-canvas/utils.d.ts +52 -0
  25. package/lib/runtime/components/react/dist/mpx-checkbox-group.d.ts +20 -0
  26. package/lib/runtime/components/react/dist/mpx-checkbox.d.ts +32 -0
  27. package/lib/runtime/components/react/dist/mpx-form.d.ts +27 -0
  28. package/lib/runtime/components/react/dist/mpx-icon/index.d.ts +18 -0
  29. package/lib/runtime/components/react/dist/mpx-image.d.ts +21 -0
  30. package/lib/runtime/components/react/dist/mpx-inline-text.d.ts +7 -0
  31. package/lib/runtime/components/react/dist/mpx-input.d.ts +50 -0
  32. package/lib/runtime/components/react/dist/mpx-input.jsx +38 -19
  33. package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.d.ts +12 -0
  34. package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +81 -48
  35. package/lib/runtime/components/react/dist/mpx-label.d.ts +20 -0
  36. package/lib/runtime/components/react/dist/mpx-movable-area.d.ts +20 -0
  37. package/lib/runtime/components/react/dist/mpx-movable-view.d.ts +63 -0
  38. package/lib/runtime/components/react/dist/mpx-nav-container.d.ts +9 -0
  39. package/lib/runtime/components/react/dist/mpx-nav-container.jsx +23 -0
  40. package/lib/runtime/components/react/dist/mpx-navigator.d.ts +9 -0
  41. package/lib/runtime/components/react/dist/mpx-picker/date.d.ts +6 -0
  42. package/lib/runtime/components/react/dist/mpx-picker/dateData.d.ts +7 -0
  43. package/lib/runtime/components/react/dist/mpx-picker/index.d.ts +6 -0
  44. package/lib/runtime/components/react/dist/mpx-picker/multiSelector.d.ts +6 -0
  45. package/lib/runtime/components/react/dist/mpx-picker/region.d.ts +6 -0
  46. package/lib/runtime/components/react/dist/mpx-picker/regionData.d.ts +2 -0
  47. package/lib/runtime/components/react/dist/mpx-picker/selector.d.ts +6 -0
  48. package/lib/runtime/components/react/dist/mpx-picker/time.d.ts +6 -0
  49. package/lib/runtime/components/react/dist/mpx-picker/type.d.ts +106 -0
  50. package/lib/runtime/components/react/dist/mpx-picker-view/index.d.ts +31 -0
  51. package/lib/runtime/components/react/dist/mpx-picker-view/pickerVIewContext.d.ts +8 -0
  52. package/lib/runtime/components/react/dist/mpx-picker-view-column/index.d.ts +22 -0
  53. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItem.d.ts +14 -0
  54. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewFaces.d.ts +16 -0
  55. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewIndicator.d.ts +12 -0
  56. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewMask.d.ts +11 -0
  57. package/lib/runtime/components/react/dist/mpx-popup/index.d.ts +22 -0
  58. package/lib/runtime/components/react/dist/mpx-popup/popupBase.d.ts +16 -0
  59. package/lib/runtime/components/react/dist/mpx-portal/index.d.ts +15 -0
  60. package/lib/runtime/components/react/dist/mpx-portal/portal-host.d.ts +29 -0
  61. package/lib/runtime/components/react/dist/mpx-portal/portal-manager.d.ts +9 -0
  62. package/lib/runtime/components/react/dist/mpx-radio-group.d.ts +20 -0
  63. package/lib/runtime/components/react/dist/mpx-radio.d.ts +26 -0
  64. package/lib/runtime/components/react/dist/mpx-rich-text/html.d.ts +1 -0
  65. package/lib/runtime/components/react/dist/mpx-rich-text/index.d.ts +24 -0
  66. package/lib/runtime/components/react/dist/mpx-root-portal.d.ts +14 -0
  67. package/lib/runtime/components/react/dist/mpx-scroll-view.d.ts +54 -0
  68. package/lib/runtime/components/react/dist/mpx-simple-text.d.ts +7 -0
  69. package/lib/runtime/components/react/dist/mpx-simple-view.d.ts +7 -0
  70. package/lib/runtime/components/react/dist/mpx-sticky-header.d.ts +17 -0
  71. package/lib/runtime/components/react/dist/mpx-sticky-section.d.ts +15 -0
  72. package/lib/runtime/components/react/dist/mpx-swiper-item.d.ts +18 -0
  73. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +1 -1
  74. package/lib/runtime/components/react/dist/mpx-swiper.d.ts +54 -0
  75. package/lib/runtime/components/react/dist/mpx-swiper.jsx +49 -5
  76. package/lib/runtime/components/react/dist/mpx-switch.d.ts +26 -0
  77. package/lib/runtime/components/react/dist/mpx-text.d.ts +21 -0
  78. package/lib/runtime/components/react/dist/mpx-textarea.d.ts +7 -0
  79. package/lib/runtime/components/react/dist/mpx-textarea.jsx +1 -0
  80. package/lib/runtime/components/react/dist/mpx-video.d.ts +101 -0
  81. package/lib/runtime/components/react/dist/mpx-view.d.ts +34 -0
  82. package/lib/runtime/components/react/dist/mpx-view.jsx +3 -2
  83. package/lib/runtime/components/react/dist/mpx-web-view.d.ts +22 -0
  84. package/lib/runtime/components/react/dist/nav.d.ts +8 -0
  85. package/lib/runtime/components/react/dist/nav.jsx +138 -0
  86. package/lib/runtime/components/react/dist/parser.d.ts +39 -0
  87. package/lib/runtime/components/react/dist/useAnimationHooks.d.ts +32 -0
  88. package/lib/runtime/components/react/dist/useNavShared.d.ts +2 -0
  89. package/lib/runtime/components/react/dist/useNavShared.js +6 -0
  90. package/lib/runtime/components/react/dist/useNodesRef.d.ts +11 -0
  91. package/lib/runtime/components/react/dist/utils.d.ts +122 -0
  92. package/lib/runtime/components/react/mpx-input.tsx +48 -26
  93. package/lib/runtime/components/react/mpx-keyboard-avoiding-view.tsx +91 -47
  94. package/lib/runtime/components/react/mpx-nav-container.tsx +33 -0
  95. package/lib/runtime/components/react/mpx-swiper-item.tsx +1 -1
  96. package/lib/runtime/components/react/mpx-swiper.tsx +60 -7
  97. package/lib/runtime/components/react/mpx-textarea.tsx +1 -0
  98. package/lib/runtime/components/react/mpx-view.tsx +3 -2
  99. package/lib/runtime/components/react/nav.tsx +164 -0
  100. package/lib/runtime/components/react/types/common.d.ts +19 -0
  101. package/lib/runtime/components/react/useNavShared.ts +8 -0
  102. package/lib/runtime/components/web/mpx-nav-container.vue +13 -0
  103. package/lib/runtime/components/wx/mpx-nav-container.mpx +9 -0
  104. package/lib/utils/dom-tag-config.js +2 -2
  105. package/package.json +1 -1
  106. package/lib/dependencies/ImportDependencyTemplate.js +0 -50
@@ -1,8 +1,9 @@
1
- import React, { ReactNode, useContext, useEffect } from 'react'
1
+ /* eslint-disable space-before-function-paren */
2
+ import React, { ReactNode, useContext, useEffect, useRef } from 'react'
2
3
  import { DimensionValue, EmitterSubscription, Keyboard, View, ViewStyle, NativeSyntheticEvent, NativeTouchEvent } from 'react-native'
3
- import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated'
4
+ import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing, cancelAnimation } from 'react-native-reanimated'
4
5
  import { KeyboardAvoidContext } from './context'
5
- import { isIOS } from './utils'
6
+ import { isAndroid, isIOS } from './utils'
6
7
 
7
8
  type KeyboardAvoidViewProps = {
8
9
  children?: ReactNode
@@ -18,15 +19,40 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
18
19
  const basic = useSharedValue('auto')
19
20
  const keyboardAvoid = useContext(KeyboardAvoidContext)
20
21
 
22
+ // fix: 某些特殊机型下隐藏键盘可能会先触发一次 keyboardWillShow,
23
+ // 比如机型 iPhone 11 Pro,可能会导致显隐动画冲突
24
+ // 因此增加状态标记 + clearTimeout + cancelAnimation 来优化
25
+ const isShow = useRef<boolean>(false)
26
+ const timerRef = useRef<NodeJS.Timeout | null>(null)
27
+ const keybaordHandleTimerRef = useRef<NodeJS.Timeout | null>(null)
28
+
21
29
  const animatedStyle = useAnimatedStyle(() => ({
30
+ // translate/position top可能会导致底部渲染区域缺失(需要 android 配置聚焦时禁用高度缩小),margin-top 会导致 portal 的定位失效,无法顶起 portal
22
31
  transform: [{ translateY: -offset.value }],
23
32
  flexBasis: basic.value as DimensionValue
24
33
  }))
25
34
 
26
35
  const resetKeyboard = () => {
36
+ if (!isShow.current) {
37
+ return
38
+ }
39
+
40
+ isShow.current = false
41
+ timerRef.current && clearTimeout(timerRef.current)
42
+
27
43
  if (keyboardAvoid?.current) {
28
- keyboardAvoid.current = null
44
+ const inputRef = keyboardAvoid.current.ref?.current
45
+ if (inputRef && inputRef.isFocused() && !keyboardAvoid.current.readyToShow) {
46
+ // 修复 Android 点击键盘收起按钮时当前 input 没触发失焦的问题
47
+ // keyboardAvoid.current.readyToShow = true 表示聚焦到了新的输入框,不需要手动触发失焦
48
+ inputRef.blur()
49
+ }
50
+ if (!keyboardAvoid.current.onKeyboardShow) {
51
+ keyboardAvoid.current = null
52
+ }
29
53
  }
54
+
55
+ cancelAnimation(offset)
30
56
  offset.value = withTiming(0, { duration, easing })
31
57
  basic.value = 'auto'
32
58
  }
@@ -40,68 +66,86 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
40
66
  useEffect(() => {
41
67
  let subscriptions: EmitterSubscription[] = []
42
68
 
43
- if (isIOS) {
44
- subscriptions = [
45
- Keyboard.addListener('keyboardWillShow', (evt: any) => {
46
- if (!keyboardAvoid?.current) return
47
- const { endCoordinates } = evt
48
- const { ref, cursorSpacing = 0 } = keyboardAvoid.current
49
- setTimeout(() => {
50
- ref?.current?.measure((x: number, y: number, width: number, height: number, pageX: number, pageY: number) => {
69
+ // iphone 16 pro & iphone 其它 pro 上长按可能会触发两次 keyboard show 事件,第一次键盘高度错误,第二次正确,因此不能用节流、仅首次生效等操作控制 show 事件
70
+ function keybaordAvoding(evt: any, ios = false) {
71
+ if (keyboardAvoid?.current?.readyToShow) {
72
+ // 重置标记位
73
+ keyboardAvoid.current.readyToShow = false
74
+ }
75
+
76
+ if (!keyboardAvoid?.current) {
77
+ return
78
+ }
79
+
80
+ isShow.current = true
81
+
82
+ if (ios) {
83
+ timerRef.current && clearTimeout(timerRef.current)
84
+ }
85
+
86
+ const { endCoordinates } = evt
87
+ const { ref, cursorSpacing = 0, adjustPosition, onKeyboardShow } = keyboardAvoid.current
88
+ keyboardAvoid.current.keyboardHeight = endCoordinates.height
89
+ onKeyboardShow?.()
90
+ if (adjustPosition) {
91
+ // 默认沿用旧版本逻辑,在 android 原生关闭键盘避让的情况下应该将该配置设置为 false,走 mpx 的键盘避让逻辑,否则bundle内的所有input都会无法避让键盘
92
+ const enableNativeKeyboardAvoiding = mpxGlobal?.__mpx?.config?.rnConfig?.enableNativeKeyboardAvoiding ?? true
93
+ const callback = () => {
94
+ ref?.current?.measure((x: number, y: number, width: number, height: number, pageX: number, pageY: number) => {
95
+ function calculateOffset() {
96
+ // enableNativeKeyboardAvoding 默认开启
97
+ if (enableNativeKeyboardAvoiding && isAndroid) {
98
+ const aboveOffset = pageY + height - endCoordinates.screenY
99
+ const belowOffset = endCoordinates.height - aboveOffset
100
+ const aboveValue = -aboveOffset >= cursorSpacing ? 0 : aboveOffset + cursorSpacing
101
+ const belowValue = Math.min(belowOffset, cursorSpacing)
102
+ return aboveOffset > 0 ? belowValue : aboveValue
103
+ }
104
+
51
105
  const aboveOffset = offset.value + pageY + height - endCoordinates.screenY
52
106
  const aboveValue = -aboveOffset >= cursorSpacing ? 0 : aboveOffset + cursorSpacing
53
107
  const belowValue = Math.min(endCoordinates.height, aboveOffset + cursorSpacing)
54
- const value = aboveOffset > 0 ? belowValue : aboveValue
55
- offset.value = withTiming(value, { duration, easing }, (finished) => {
56
- if (finished) {
57
- // Set flexBasic after animation to trigger re-layout and reset layout information
58
- basic.value = '99.99%'
59
- }
60
- })
61
- })
62
- })
63
- }),
64
- Keyboard.addListener('keyboardWillHide', resetKeyboard)
65
- ]
66
- } else {
67
- subscriptions = [
68
- Keyboard.addListener('keyboardDidShow', (evt: any) => {
69
- if (!keyboardAvoid?.current) return
70
- const { endCoordinates } = evt
71
- const { ref, cursorSpacing = 0 } = keyboardAvoid.current
72
- ref?.current?.measure((x: number, y: number, width: number, height: number, pageX: number, pageY: number) => {
73
- const aboveOffset = pageY + height - endCoordinates.screenY
74
- const belowOffset = endCoordinates.height - aboveOffset
75
- const aboveValue = -aboveOffset >= cursorSpacing ? 0 : aboveOffset + cursorSpacing
76
- const belowValue = Math.min(belowOffset, cursorSpacing)
77
- const value = aboveOffset > 0 ? belowValue : aboveValue
78
- offset.value = withTiming(value, { duration, easing }, (finished) => {
108
+ return aboveOffset > 0 ? belowValue : aboveValue
109
+ }
110
+
111
+ cancelAnimation(offset)
112
+ offset.value = withTiming(calculateOffset(), { duration, easing }, finished => {
79
113
  if (finished) {
80
114
  // Set flexBasic after animation to trigger re-layout and reset layout information
81
115
  basic.value = '99.99%'
82
116
  }
83
117
  })
84
118
  })
119
+ }
120
+ ;(isIOS ? () => (timerRef.current = setTimeout(callback)) : callback)()
121
+ }
122
+ }
123
+
124
+ if (isIOS) {
125
+ subscriptions = [
126
+ Keyboard.addListener('keyboardWillShow', (evt: any) => {
127
+ if (keybaordHandleTimerRef.current) {
128
+ clearTimeout(keybaordHandleTimerRef.current)
129
+ }
130
+ // iphone 在input聚焦时长按滑动后会导致 show 事件先于 focus 事件发生,因此等一下,等 focus 先触发拿到 input,避免键盘出现但input没顶上去
131
+ keybaordHandleTimerRef.current = setTimeout(() => keybaordAvoding(evt, true), 32)
85
132
  }),
86
- Keyboard.addListener('keyboardDidHide', resetKeyboard)
133
+ Keyboard.addListener('keyboardWillHide', resetKeyboard)
87
134
  ]
135
+ } else {
136
+ subscriptions = [Keyboard.addListener('keyboardDidShow', keybaordAvoding), Keyboard.addListener('keyboardDidHide', resetKeyboard)]
88
137
  }
89
138
 
90
139
  return () => {
91
140
  subscriptions.forEach(subscription => subscription.remove())
141
+ timerRef.current && clearTimeout(timerRef.current)
142
+ keybaordHandleTimerRef.current && clearTimeout(keybaordHandleTimerRef.current)
92
143
  }
93
144
  }, [keyboardAvoid])
94
145
 
95
146
  return (
96
- <View style={style} onTouchEnd={onTouchEnd}>
97
- <Animated.View
98
- style={[
99
- contentContainerStyle,
100
- animatedStyle
101
- ]}
102
- >
103
- {children}
104
- </Animated.View>
147
+ <View style={style} onTouchEnd={onTouchEnd} onTouchMove={onTouchEnd}>
148
+ <Animated.View style={[contentContainerStyle, animatedStyle]}>{children}</Animated.View>
105
149
  </View>
106
150
  )
107
151
  }
@@ -0,0 +1,33 @@
1
+ import { AnimatedStyle } from 'react-native-reanimated'
2
+ import { useNavShared } from './useNavShared'
3
+ import { NavSharedContext, NavSharedValue } from './context'
4
+ import { useLayoutEffect, useMemo, useState } from 'react'
5
+ import { StyleProp } from 'react-native'
6
+ import { isAndroid } from './utils'
7
+
8
+ interface MpxNavContainerProps {
9
+ children?: React.ReactNode
10
+ }
11
+
12
+ export default function MpxNavContainer (props: MpxNavContainerProps) {
13
+ const [, setCustomNav] = useNavShared()
14
+
15
+ useLayoutEffect(() => {
16
+ if (!isAndroid) return
17
+ if (props.children) {
18
+ setCustomNav(props.children)
19
+ }
20
+
21
+ return () => {
22
+ setCustomNav(undefined)
23
+ }
24
+ }, [props.children])
25
+
26
+ return isAndroid ? null : props.children
27
+ }
28
+
29
+ export function NavSharedProvider ({ children }: { children?: React.ReactNode }) {
30
+ const [customNav, setCustomNav] = useState()
31
+ const value = useMemo(() => ({ customNav, setCustomNav } as NavSharedValue), [customNav])
32
+ return <NavSharedContext.Provider value={value}>{children}</NavSharedContext.Provider>
33
+ }
@@ -84,7 +84,7 @@ const _SwiperItem = forwardRef<HandlerRef<View, SwiperItemProps>, SwiperItemProp
84
84
  const inputRange = [step.value, 0]
85
85
  const outputRange = [0.7, 1]
86
86
  // 实现元素的宽度跟随step从0到真实宽度,且不能触发重新渲染整个组件,通过AnimatedStyle的方式实现
87
- const outerLayoutStyle = dir === 'x' ? { width: step.value, height: '100%' } : { width: '100%', height: step.value }
87
+ const outerLayoutStyle = dir === 'x' ? { width: step.value || '100%', height: '100%' } : { width: '100%', height: step.value || '100%' }
88
88
  const transformStyle = []
89
89
  if (scale) {
90
90
  transformStyle.push({
@@ -5,7 +5,7 @@ import Animated, { useAnimatedStyle, useSharedValue, withTiming, Easing, runOnJS
5
5
  import React, { JSX, forwardRef, useRef, useEffect, ReactNode, ReactElement, useMemo, createElement } from 'react'
6
6
  import useInnerProps, { getCustomEvent } from './getInnerListeners'
7
7
  import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数
8
- import { useTransformStyle, splitStyle, splitProps, useLayout, wrapChildren, extendObject, GestureHandler, flatGesture, useRunOnJSCallback } from './utils'
8
+ import { useTransformStyle, splitStyle, splitProps, useLayout, wrapChildren, extendObject, GestureHandler, flatGesture, useRunOnJSCallback, isHarmony } from './utils'
9
9
  import { SwiperContext } from './context'
10
10
  import Portal from './mpx-portal'
11
11
  /**
@@ -33,6 +33,11 @@ type EventDataType = {
33
33
  // onUpdate时根据上一个判断方向,onFinalize根据transformStart判断
34
34
  transdir: number
35
35
  }
36
+ // bindtransition回调的参数
37
+ type EventTransition = {
38
+ dx: number
39
+ dy: number
40
+ }
36
41
 
37
42
  interface SwiperProps {
38
43
  children?: ReactNode
@@ -64,6 +69,8 @@ interface SwiperProps {
64
69
  'simultaneous-handlers'?: Array<GestureHandler>
65
70
  disableGesture?: boolean
66
71
  bindchange?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
72
+ bindtransition?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
73
+ bindanimationfinish?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
67
74
  }
68
75
 
69
76
  /**
@@ -149,7 +156,9 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
149
156
  circular = false,
150
157
  disableGesture = false,
151
158
  current: propCurrent = 0,
152
- bindchange
159
+ bindchange,
160
+ bindtransition,
161
+ bindanimationfinish
153
162
  } = props
154
163
  const easeingFunc = props['easing-function'] || 'default'
155
164
  const easeDuration = props.duration || 500
@@ -378,6 +387,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
378
387
  }, () => {
379
388
  currentIndex.value = nextIndex
380
389
  runOnJS(runOnJSCallback)('loop')
390
+ bindanimationfinish && runOnJS(runOnJSCallback)('handleAnimationfinish', nextIndex)
381
391
  })
382
392
  } else {
383
393
  // 默认向右, 向下
@@ -393,6 +403,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
393
403
  offset.value = initOffset
394
404
  currentIndex.value = nextIndex
395
405
  runOnJS(runOnJSCallback)('loop')
406
+ bindanimationfinish && runOnJS(runOnJSCallback)('handleAnimationfinish', nextIndex)
396
407
  })
397
408
  } else {
398
409
  nextIndex = currentIndex.value + 1
@@ -404,6 +415,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
404
415
  }, () => {
405
416
  currentIndex.value = nextIndex
406
417
  runOnJS(runOnJSCallback)('loop')
418
+ runOnJS(runOnJSCallback)('handleAnimationfinish', nextIndex)
407
419
  })
408
420
  }
409
421
  }
@@ -436,11 +448,23 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
436
448
  bindchange && bindchange(eventData)
437
449
  }
438
450
 
451
+ function handleTransition (transData: EventTransition) {
452
+ const eventData = getCustomEvent('change', {}, { detail: transData, layoutRef: layoutRef })
453
+ bindtransition && bindtransition(eventData)
454
+ }
455
+
456
+ function handleAnimationfinish (current: number) {
457
+ const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef })
458
+ bindanimationfinish && bindanimationfinish(eventData)
459
+ }
460
+
439
461
  const runOnJSCallbackRef = useRef({
440
462
  loop,
441
463
  pauseLoop,
442
464
  resumeLoop,
443
- handleSwiperChange
465
+ handleSwiperChange,
466
+ handleTransition,
467
+ handleAnimationfinish
444
468
  })
445
469
  const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef)
446
470
 
@@ -466,6 +490,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
466
490
  easing: easeMap[easeingFunc]
467
491
  }, () => {
468
492
  currentIndex.value = propCurrent
493
+ bindanimationfinish && runOnJS(runOnJSCallback)('handleAnimationfinish', propCurrent)
469
494
  })
470
495
  } else {
471
496
  offset.value = targetOffset
@@ -486,7 +511,27 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
486
511
  runOnJS(runOnJSCallback)('handleSwiperChange', newIndex, propCurrent)
487
512
  }
488
513
  })
489
-
514
+ useAnimatedReaction(() => offset.value, (curOffset, preOffset) => {
515
+ const curAbsOffset = Math.abs(curOffset)
516
+ const preAbsOffset = Math.abs(preOffset || 0)
517
+ const computeOffset = step.value * (currentIndex.value + patchElmNumShared.value)
518
+ // 有小数点的情况
519
+ const isEqual = Math.abs(Math.floor(computeOffset) - Math.floor(Math.abs(offset.value))) <= 2
520
+ if (curAbsOffset !== preAbsOffset && curAbsOffset && !isEqual) {
521
+ // 移动的距离,手向左滑动正数( curAbsOffset >= preAbsOffset),右右滑动是负数( curAbsOffset < preAbsOffset)
522
+ let trans = 0
523
+ if (curAbsOffset >= preAbsOffset) {
524
+ trans = curAbsOffset % step.value
525
+ } else {
526
+ trans = -(step.value - curAbsOffset % step.value)
527
+ }
528
+ const transData = {
529
+ dx: dir === 'x' ? trans : 0,
530
+ dy: dir === 'y' ? trans : 0
531
+ }
532
+ bindtransition && runOnJS(runOnJSCallback)('handleTransition', transData)
533
+ }
534
+ })
490
535
  useEffect(() => {
491
536
  let patchStep = 0
492
537
  if (preMargin !== preMarginShared.value) {
@@ -613,6 +658,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
613
658
  currentIndex.value = selectedIndex
614
659
  offset.value = resetOffset
615
660
  runOnJS(runOnJSCallback)('resumeLoop')
661
+ bindanimationfinish && runOnJS(runOnJSCallback)('handleAnimationfinish', selectedIndex)
616
662
  }
617
663
  })
618
664
  } else {
@@ -623,6 +669,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
623
669
  if (touchfinish.value !== false) {
624
670
  currentIndex.value = selectedIndex
625
671
  runOnJS(runOnJSCallback)('resumeLoop')
672
+ bindanimationfinish && runOnJS(runOnJSCallback)('handleAnimationfinish', selectedIndex)
626
673
  }
627
674
  })
628
675
  }
@@ -645,6 +692,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
645
692
  if (touchfinish.value !== false) {
646
693
  currentIndex.value = moveToIndex
647
694
  runOnJS(runOnJSCallback)('resumeLoop')
695
+ bindanimationfinish && runOnJS(runOnJSCallback)('handleAnimationfinish', moveToIndex)
648
696
  }
649
697
  })
650
698
  }
@@ -791,6 +839,10 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
791
839
  }
792
840
  preAbsolutePos.value = e[strAbso]
793
841
  })
842
+ .onEnd((e: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
843
+ // 修复某些安卓机型小米 onFinalize拿到的absolute值不正确的问题, onUpdate并不是最终的值
844
+ preAbsolutePos.value = e[strAbso]
845
+ })
794
846
  .onFinalize((e: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
795
847
  'worklet'
796
848
  if (touchfinish.value) return
@@ -855,10 +907,11 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
855
907
  }, [gestureSwitch.current])
856
908
 
857
909
  const animatedStyles = useAnimatedStyle(() => {
910
+ const opacity = isHarmony ? 1 : (step.value > 0 ? 1 : 0)
858
911
  if (dir === 'x') {
859
- return { transform: [{ translateX: offset.value }], opacity: step.value > 0 ? 1 : 0 }
912
+ return { transform: [{ translateX: offset.value }], opacity }
860
913
  } else {
861
- return { transform: [{ translateY: offset.value }], opacity: step.value > 0 ? 1 : 0 }
914
+ return { transform: [{ translateY: offset.value }], opacity }
862
915
  }
863
916
  })
864
917
 
@@ -891,4 +944,4 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
891
944
  })
892
945
  SwiperWrapper.displayName = 'MpxSwiperWrapper'
893
946
 
894
- export default SwiperWrapper
947
+ export default SwiperWrapper
@@ -4,6 +4,7 @@
4
4
  * type, password, confirm-hold
5
5
  * Addition:
6
6
  * ✔ confirm-type
7
+ * ✘ confirm-hold
7
8
  * ✔ auto-height
8
9
  * ✘ fixed
9
10
  * ✘ show-confirm-bar
@@ -287,7 +287,7 @@ function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, ima
287
287
  } else { // 数值类型 ImageStyle
288
288
  // 数值类型设置为 stretch
289
289
  imageProps.resizeMode = 'stretch'
290
- if (type === 'linear' && (!layoutWidth || !layoutHeight)) {
290
+ if (type === 'linear' && (!layoutWidth || !layoutHeight) && isNeedLayout(preImageInfo)) {
291
291
  // ios 上 linear 组件只要重新触发渲染,在渲染过程中外层容器 width 或者 height 被设置为 0,通过设置 % 的方式会渲染不出来,即使后面再更新为正常宽高也渲染不出来
292
292
  // 所以 hack 手动先将 linear 宽高也设置为 0,后面再更新为正确的数值或 %。
293
293
  dimensions = {
@@ -295,6 +295,7 @@ function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, ima
295
295
  height: 0
296
296
  } as { width: NumberVal, height: NumberVal }
297
297
  } else {
298
+ // background-size 手动设置具体值,不会触发 onLayout,需要走这里的逻辑
298
299
  dimensions = {
299
300
  width: isPercent(width) ? width : +width,
300
301
  height: isPercent(height) ? height : +height
@@ -501,7 +502,7 @@ function parseBgImage (text: string): {
501
502
  type?: 'image' | 'linear'
502
503
  src?: string
503
504
  } {
504
- if (!text) return {}
505
+ if (!text || text === 'none') return {}
505
506
 
506
507
  const src = parseUrl(text)
507
508
  if (src) return { src, type: 'image' }
@@ -0,0 +1,164 @@
1
+ /* eslint-disable space-before-function-paren */
2
+ import { createElement, useState, useMemo, memo, useContext, useLayoutEffect } from 'react'
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
4
+ import { StatusBar, processColor, TouchableWithoutFeedback, Image, View, StyleSheet, Text } from 'react-native'
5
+ import { useNavShared } from './useNavShared'
6
+
7
+ function convertToHex(color?: string) {
8
+ try {
9
+ const intColor = processColor(color) as number | null | undefined
10
+ if (intColor === null || intColor === undefined) {
11
+ return null
12
+ }
13
+ // 将32位整数颜色值转换为RGBA
14
+ const r = (intColor >> 16) & 255
15
+ const g = (intColor >> 8) & 255
16
+ const b = intColor & 255
17
+ // 转换为十六进制
18
+ const hexR = r.toString(16).padStart(2, '0')
19
+ const hexG = g.toString(16).padStart(2, '0')
20
+ const hexB = b.toString(16).padStart(2, '0')
21
+ return `#${hexR}${hexG}${hexB}`
22
+ } catch (error) {
23
+ return null
24
+ }
25
+ }
26
+
27
+ const titleHeight = 44
28
+ export function useInnerHeaderHeight(pageConfig: PageConfig) {
29
+ const safeArea = useSafeAreaInsets()
30
+ if (pageConfig.navigationStyle === 'custom') {
31
+ return 0
32
+ } else {
33
+ const safeAreaTop = safeArea?.top || 0
34
+ const headerHeight = safeAreaTop + titleHeight
35
+ return headerHeight
36
+ }
37
+ }
38
+
39
+ const styles = StyleSheet.create({
40
+ header: {
41
+ elevation: 3
42
+ },
43
+ headerContent: {
44
+ flexDirection: 'row',
45
+ alignItems: 'center',
46
+ justifyContent: 'center'
47
+ },
48
+ backButton: {
49
+ position: 'absolute',
50
+ height: '100%',
51
+ width: 40,
52
+ left: 0,
53
+ top: 0,
54
+ alignItems: 'center',
55
+ justifyContent: 'center'
56
+ },
57
+ backButtonImage: {
58
+ width: 22,
59
+ height: 22
60
+ },
61
+ title: {
62
+ fontSize: 17,
63
+ fontWeight: 600,
64
+ width: '60%',
65
+ textAlign: 'center'
66
+ }
67
+ })
68
+ const NavColor = {
69
+ White: '#ffffff',
70
+ Black: '#000000'
71
+ }
72
+ // navigationBarTextStyle 只支持黑白 'white'/'black
73
+ const validBarTextStyle = (textStyle?: string) => {
74
+ const textStyleColor = convertToHex(textStyle)
75
+ if (textStyle && textStyleColor && [NavColor.White, NavColor.Black].includes(textStyleColor)) {
76
+ return textStyleColor
77
+ } else {
78
+ return NavColor.White
79
+ }
80
+ }
81
+
82
+ export interface MpxNavProps {
83
+ pageConfig: PageConfig
84
+ navigation: any
85
+ }
86
+
87
+ const BACK_ICON =
88
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAABICAYAAACqT5alAAAA2UlEQVR4nO3bMQrCUBRE0Yla6AYEN2nnBrTL+izcitW3MRDkEUWSvPzJvfCqgMwhZbAppWhNbbIHzB1g9wATERFRVyvpkj1irlpJ5X326D7WHh1hbdFD2CLpLmmftm7kfsEe09aNHFiBrT+wAlt/YAW2/sAKbP2BFdj6Ayuwy+ufz6XPL893krZ//O6iu2n4LT8kndLWTRTo4EC7BDo40C6BDg60S6CDA+0S6OBAuwQ6uNWiD2nrJmoIfU7cNWkR2hbb1UfbY7uuWhGWiIg+a/iHuHmA3QPs3gu4JW9Gan+OJAAAAABJRU5ErkJggg=='
89
+
90
+ const MpxNav = memo(({ pageConfig, navigation }: MpxNavProps) => {
91
+ const [innerPageConfig, setPageConfig] = useState<PageConfig>(pageConfig || {})
92
+ const [customNav] = useNavShared()
93
+ const safeAreaTop = useSafeAreaInsets()?.top || 0
94
+
95
+ navigation.setPageConfig = (config: PageConfig) => {
96
+ setPageConfig(Object.assign({}, innerPageConfig, config))
97
+ }
98
+ const isCustom = innerPageConfig.navigationStyle === 'custom'
99
+ const navigationBarTextStyle = useMemo(() => validBarTextStyle(innerPageConfig.navigationBarTextStyle), [innerPageConfig.navigationBarTextStyle])
100
+ // 状态栏的颜色
101
+ const statusBarElement = (
102
+ <StatusBar
103
+ translucent
104
+ backgroundColor='transparent'
105
+ barStyle={navigationBarTextStyle === NavColor.White ? 'light-content' : 'dark-content'}></StatusBar>
106
+ )
107
+
108
+ if (isCustom) {
109
+ return (
110
+ <>
111
+ {statusBarElement}
112
+ {customNav}
113
+ </>
114
+ )
115
+ }
116
+ // 假设是栈导航,获取栈的长度
117
+ const stackLength = navigation.getState()?.routes?.length
118
+ const onStackTopBack = mpxGlobal?.__mpx?.config?.rnConfig?.onStackTopBack
119
+ const isHandleStackTopBack = typeof onStackTopBack === 'function'
120
+
121
+ // 回退按钮与图标
122
+ // prettier-ignore
123
+ const backElement = stackLength > 1 || isHandleStackTopBack
124
+ ? (
125
+ <TouchableWithoutFeedback
126
+ onPress={() => {
127
+ if (stackLength <= 1 && isHandleStackTopBack) {
128
+ onStackTopBack()
129
+ return
130
+ }
131
+ navigation.goBack()
132
+ }}>
133
+ <View style={[styles.backButton]}>
134
+ <Image style={[styles.backButtonImage, { tintColor: navigationBarTextStyle }]} source={{ uri: BACK_ICON }}></Image>
135
+ </View>
136
+ </TouchableWithoutFeedback>
137
+ )
138
+ : null
139
+
140
+ return (
141
+ <View
142
+ style={[
143
+ styles.header,
144
+ {
145
+ paddingTop: safeAreaTop,
146
+ backgroundColor: innerPageConfig.navigationBarBackgroundColor || '#000000'
147
+ }
148
+ ]}>
149
+ {statusBarElement}
150
+ {/* TODO: 确定 height 的有效性 */}
151
+ {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
152
+ {/* @ts-expect-error */}
153
+ <View style={styles.headerContent} height={titleHeight}>
154
+ {backElement}
155
+ <Text style={[styles.title, { color: navigationBarTextStyle }]} numberOfLines={1}>
156
+ {innerPageConfig.navigationBarTitleText?.trim() || ''}
157
+ </Text>
158
+ </View>
159
+ </View>
160
+ )
161
+ })
162
+
163
+ MpxNav.displayName = 'MpxNav'
164
+ export default MpxNav
@@ -18,3 +18,22 @@ export type ExtendedFunctionComponent = FunctionComponent & {
18
18
  }
19
19
 
20
20
  export type AnyFunc = (...args: ReadonlyArray<any>) => any
21
+
22
+ declare global {
23
+ interface PageConfig {
24
+ /**
25
+ * 是否自定义导航栏
26
+ */
27
+ navigationStyle?: 'custom'
28
+ /**
29
+ * 标题栏样式
30
+ */
31
+ navigationBarTextStyle?: 'white' | 'black' | '#ffffff' | '#000000'
32
+ /**
33
+ * 页面标题
34
+ */
35
+ navigationBarTitleText?: string
36
+
37
+ [key: string]: any
38
+ }
39
+ }
@@ -0,0 +1,8 @@
1
+ import { useContext } from 'react'
2
+ import { NavSharedContext } from './context'
3
+
4
+ export function useNavShared () {
5
+ const navSharedValue = useContext(NavSharedContext)
6
+
7
+ return [navSharedValue.customNav, navSharedValue.setCustomNav] as const
8
+ }
@@ -0,0 +1,13 @@
1
+ <script>
2
+ export default {
3
+ name: 'mpx-view',
4
+ data() {
5
+ return {}
6
+ },
7
+ props: {},
8
+ render() {
9
+ return this.$slots.default
10
+ },
11
+ methods: {}
12
+ }
13
+ </script>
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <slot />
3
+ </template>
4
+ <script type="application/json">
5
+ {
6
+ "component": true,
7
+ "usingComponents": {}
8
+ }
9
+ </script>