@mpxjs/webpack-plugin 2.10.9 → 2.10.11-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/independent-loader.js +0 -1
- package/lib/platform/template/wx/component-config/index.js +2 -0
- package/lib/platform/template/wx/component-config/page-container.js +19 -0
- package/lib/platform/template/wx/component-config/unsupported.js +1 -1
- package/lib/runtime/components/react/dist/mpx-page-container.jsx +300 -0
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +40 -9
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +3 -3
- package/lib/runtime/components/react/mpx-page-container.tsx +392 -0
- package/lib/runtime/components/react/mpx-scroll-view.tsx +41 -9
- package/lib/runtime/components/react/mpx-swiper.tsx +4 -3
- package/lib/runtime/components/react/useNodesRef.ts +0 -1
- package/lib/template-compiler/compiler.js +23 -3
- package/lib/utils/dom-tag-config.js +1 -1
- package/package.json +2 -2
- package/LICENSE +0 -433
|
@@ -0,0 +1,392 @@
|
|
|
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
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
import { ScrollView, RefreshControl, Gesture, GestureDetector } from 'react-native-gesture-handler'
|
|
35
35
|
import { View, NativeSyntheticEvent, NativeScrollEvent, LayoutChangeEvent, ViewStyle, Animated as RNAnimated } from 'react-native'
|
|
36
36
|
import { isValidElement, Children, JSX, ReactNode, RefObject, useRef, useState, useEffect, forwardRef, useContext, useMemo, createElement } from 'react'
|
|
37
|
-
import Animated, {
|
|
37
|
+
import Animated, { useSharedValue, withTiming, useAnimatedStyle, runOnJS } from 'react-native-reanimated'
|
|
38
38
|
import { warn, hasOwn } from '@mpxjs/utils'
|
|
39
39
|
import useInnerProps, { getCustomEvent } from './getInnerListeners'
|
|
40
40
|
import useNodesRef, { HandlerRef } from './useNodesRef'
|
|
@@ -194,6 +194,8 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
194
194
|
white: ['#fff']
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
const isContentSizeChange = useRef(false)
|
|
198
|
+
|
|
197
199
|
const { refresherContent, otherContent } = getRefresherContent(props.children)
|
|
198
200
|
const hasRefresher = refresherContent && refresherEnabled
|
|
199
201
|
|
|
@@ -209,7 +211,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
209
211
|
|
|
210
212
|
const { textStyle, innerStyle = {} } = splitStyle(normalStyle)
|
|
211
213
|
|
|
212
|
-
const scrollViewRef =
|
|
214
|
+
const scrollViewRef = useRef<ScrollView>(null)
|
|
213
215
|
useNodesRef(props, ref, scrollViewRef, {
|
|
214
216
|
style: normalStyle,
|
|
215
217
|
scrollOffset: scrollOptions,
|
|
@@ -358,7 +360,22 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
358
360
|
}
|
|
359
361
|
|
|
360
362
|
function onContentSizeChange (width: number, height: number) {
|
|
361
|
-
|
|
363
|
+
isContentSizeChange.current = true
|
|
364
|
+
const newContentLength = selectLength({ height, width })
|
|
365
|
+
const oldContentLength = scrollOptions.current.contentLength
|
|
366
|
+
scrollOptions.current.contentLength = newContentLength
|
|
367
|
+
// 内容高度变化时,Animated.event 的映射可能会有不生效的场景,所以需要手动设置一下 scrollOffset 的值
|
|
368
|
+
if (enableSticky && (__mpx_mode__ === 'android' || __mpx_mode__ === 'ios')) {
|
|
369
|
+
// 当内容变少时,检查当前滚动位置是否超出新的内容范围
|
|
370
|
+
if (newContentLength < oldContentLength) {
|
|
371
|
+
const { visibleLength, offset } = scrollOptions.current
|
|
372
|
+
const maxOffset = Math.max(0, newContentLength - visibleLength)
|
|
373
|
+
// 如果当前滚动位置超出了新的内容范围,调整滚动offset
|
|
374
|
+
if (offset > maxOffset && scrollY) {
|
|
375
|
+
scrollOffset.setValue(maxOffset)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
362
379
|
}
|
|
363
380
|
|
|
364
381
|
function onLayout (e: LayoutChangeEvent) {
|
|
@@ -381,8 +398,9 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
381
398
|
|
|
382
399
|
function onScroll (e: NativeSyntheticEvent<NativeScrollEvent>) {
|
|
383
400
|
const { bindscroll } = props
|
|
384
|
-
const {
|
|
385
|
-
const {
|
|
401
|
+
const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent
|
|
402
|
+
const { x: scrollLeft, y: scrollTop } = contentOffset
|
|
403
|
+
const { width: scrollWidth, height: scrollHeight } = contentSize
|
|
386
404
|
isAtTop.value = scrollTop <= 0
|
|
387
405
|
bindscroll &&
|
|
388
406
|
bindscroll(
|
|
@@ -393,7 +411,8 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
393
411
|
scrollHeight,
|
|
394
412
|
scrollWidth,
|
|
395
413
|
deltaX: scrollLeft - scrollOptions.current.scrollLeft,
|
|
396
|
-
deltaY: scrollTop - scrollOptions.current.scrollTop
|
|
414
|
+
deltaY: scrollTop - scrollOptions.current.scrollTop,
|
|
415
|
+
layoutMeasurement
|
|
397
416
|
},
|
|
398
417
|
layoutRef
|
|
399
418
|
}, props)
|
|
@@ -408,8 +427,9 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
408
427
|
|
|
409
428
|
function onScrollEnd (e: NativeSyntheticEvent<NativeScrollEvent>) {
|
|
410
429
|
const { bindscrollend } = props
|
|
411
|
-
const {
|
|
412
|
-
const {
|
|
430
|
+
const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent
|
|
431
|
+
const { x: scrollLeft, y: scrollTop } = contentOffset
|
|
432
|
+
const { width: scrollWidth, height: scrollHeight } = contentSize
|
|
413
433
|
isAtTop.value = scrollTop <= 0
|
|
414
434
|
bindscrollend &&
|
|
415
435
|
bindscrollend(
|
|
@@ -418,7 +438,8 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
418
438
|
scrollLeft,
|
|
419
439
|
scrollTop,
|
|
420
440
|
scrollHeight,
|
|
421
|
-
scrollWidth
|
|
441
|
+
scrollWidth,
|
|
442
|
+
layoutMeasurement
|
|
422
443
|
},
|
|
423
444
|
layoutRef
|
|
424
445
|
}, props)
|
|
@@ -505,6 +526,16 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
505
526
|
{
|
|
506
527
|
useNativeDriver: true,
|
|
507
528
|
listener: (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
529
|
+
const y = event.nativeEvent.contentOffset.y || 0
|
|
530
|
+
// 内容高度变化时,鸿蒙中 listener 回调通过scrollOffset.__getValue获取值一直等于event.nativeEvent.contentOffset.y,值是正确的,但是无法触发 sticky 动画执行,所以需要手动再 set 一次
|
|
531
|
+
if (__mpx_mode__ === 'harmony') {
|
|
532
|
+
if (isContentSizeChange.current) {
|
|
533
|
+
scrollOffset.setValue(y)
|
|
534
|
+
setTimeout(() => {
|
|
535
|
+
isContentSizeChange.current = false
|
|
536
|
+
}, 100)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
508
539
|
onScroll(event)
|
|
509
540
|
}
|
|
510
541
|
}
|
|
@@ -683,6 +714,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
|
|
|
683
714
|
showsVerticalScrollIndicator: scrollY && showScrollbar,
|
|
684
715
|
scrollEnabled: !enableScroll ? false : !!(scrollX || scrollY),
|
|
685
716
|
bounces: false,
|
|
717
|
+
overScrollMode: 'never',
|
|
686
718
|
ref: scrollViewRef,
|
|
687
719
|
onScroll: enableSticky ? scrollHandler : onScroll,
|
|
688
720
|
onContentSizeChange: onContentSizeChange,
|
|
@@ -143,7 +143,8 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
143
143
|
style = {},
|
|
144
144
|
autoplay = false,
|
|
145
145
|
circular = false,
|
|
146
|
-
disableGesture = false
|
|
146
|
+
disableGesture = false,
|
|
147
|
+
bindchange
|
|
147
148
|
} = props
|
|
148
149
|
const easeingFunc = props['easing-function'] || 'default'
|
|
149
150
|
const easeDuration = props.duration || 500
|
|
@@ -427,7 +428,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
427
428
|
function handleSwiperChange (current: number) {
|
|
428
429
|
if (props.current !== currentIndex.value) {
|
|
429
430
|
const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef })
|
|
430
|
-
|
|
431
|
+
bindchange && bindchange(eventData)
|
|
431
432
|
}
|
|
432
433
|
}
|
|
433
434
|
|
|
@@ -469,7 +470,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
469
470
|
// 1. 用户在当前页切换选中项,动画;用户携带选中index打开到swiper页直接选中不走动画
|
|
470
471
|
useAnimatedReaction(() => currentIndex.value, (newIndex: number, preIndex: number) => {
|
|
471
472
|
// 这里必须传递函数名, 直接写()=> {}形式会报 访问了未sharedValue信息
|
|
472
|
-
if (newIndex !== preIndex &&
|
|
473
|
+
if (newIndex !== preIndex && bindchange) {
|
|
473
474
|
runOnJS(handleSwiperChange)(newIndex)
|
|
474
475
|
}
|
|
475
476
|
})
|
|
@@ -38,7 +38,7 @@ const endTag = new RegExp(('^<\\/' + qnameCapture + '[^>]*>'))
|
|
|
38
38
|
const doctype = /^<!DOCTYPE [^>]+>/i
|
|
39
39
|
const comment = /^<!--/
|
|
40
40
|
const conditionalComment = /^<!\[/
|
|
41
|
-
const specialClassReg = /^mpx-((cover-)?view|button|navigator|picker-view|input|textarea)$/
|
|
41
|
+
const specialClassReg = /^mpx-((cover-)?view|button|navigator|picker-view|input|textarea|page-container)$/
|
|
42
42
|
let IS_REGEX_CAPTURING_BROKEN = false
|
|
43
43
|
'x'.replace(/x(.)?/g, function (m, g) {
|
|
44
44
|
IS_REGEX_CAPTURING_BROKEN = g === ''
|
|
@@ -1111,8 +1111,28 @@ function processStyleReact (el, options) {
|
|
|
1111
1111
|
}])
|
|
1112
1112
|
}
|
|
1113
1113
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1114
|
+
// 原生组件支持 xx-class 与 xx-style(xx-class 将会被合并到 xx-style 中)
|
|
1115
|
+
const match = el.tag.match(specialClassReg)
|
|
1116
|
+
if (match) {
|
|
1117
|
+
let staticClassNames
|
|
1118
|
+
switch (el.tag) {
|
|
1119
|
+
case 'mpx-view':
|
|
1120
|
+
case 'mpx-cover-view':
|
|
1121
|
+
case 'mpx-button':
|
|
1122
|
+
case 'mpx-navigator':
|
|
1123
|
+
staticClassNames = ['hover']
|
|
1124
|
+
break
|
|
1125
|
+
case 'mpx-page-container':
|
|
1126
|
+
staticClassNames = ['custom', 'overlay']
|
|
1127
|
+
break
|
|
1128
|
+
case 'mpx-input':
|
|
1129
|
+
case 'mpx-textarea':
|
|
1130
|
+
staticClassNames = ['placeholder']
|
|
1131
|
+
break
|
|
1132
|
+
case 'mpx-picker-view':
|
|
1133
|
+
staticClassNames = ['mask', 'indicator']
|
|
1134
|
+
break
|
|
1135
|
+
}
|
|
1116
1136
|
staticClassNames.forEach((className) => {
|
|
1117
1137
|
let staticClass = el.attrsMap[className + '-class'] || ''
|
|
1118
1138
|
let staticStyle = getAndRemoveAttr(el, className + '-style').val || ''
|
|
@@ -78,7 +78,7 @@ const isBuildInWebTag = makeMap(
|
|
|
78
78
|
'mpx-swiper,mpx-view,mpx-checkbox-group,mpx-movable-area,mpx-radio-group,' +
|
|
79
79
|
'mpx-switch,mpx-web-view,mpx-checkbox,mpx-movable-view,mpx-radio,' +
|
|
80
80
|
'mpx-tab-bar-container,mpx-form,mpx-navigator,mpx-rich-text,mpx-tab-bar,' +
|
|
81
|
-
'mpx-icon,mpx-picker-view-column,mpx-scroll-view,mpx-text'
|
|
81
|
+
'mpx-icon,mpx-picker-view-column,mpx-scroll-view,mpx-text,mpx-page-container'
|
|
82
82
|
)
|
|
83
83
|
|
|
84
84
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpxjs/webpack-plugin",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.11-beta.1",
|
|
4
4
|
"description": "mpx compile core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mpx"
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"engines": {
|
|
101
101
|
"node": ">=14.14.0"
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "2d37697869b9bdda3efab92dda8c910b68fd05c0"
|
|
104
104
|
}
|