@mpxjs/webpack-plugin 2.10.17-unocss.1 → 2.10.18-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/index.js +49 -36
- package/lib/platform/style/wx/index.js +0 -2
- package/lib/platform/template/wx/component-config/camera.js +12 -0
- package/lib/platform/template/wx/component-config/unsupported.js +1 -1
- package/lib/react/processStyles.js +8 -39
- package/lib/react/style-helper.js +5 -16
- package/lib/resolver/ExtendComponentsPlugin.js +60 -0
- package/lib/runtime/components/ali/mpx-section-list.mpx +566 -0
- package/lib/runtime/components/ali/mpx-sticky-header.mpx +212 -0
- package/lib/runtime/components/ali/mpx-sticky-section.mpx +17 -0
- package/lib/runtime/components/react/dist/animationHooks/useAnimationAPIHooks.js +0 -1
- package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +2 -1
- package/lib/runtime/components/react/dist/mpx-input.jsx +10 -3
- package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +13 -2
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +60 -40
- package/lib/runtime/components/react/dist/utils.jsx +8 -4
- package/lib/runtime/components/react/mpx-async-suspense.tsx +2 -1
- package/lib/runtime/components/react/mpx-camera.tsx +327 -0
- package/lib/runtime/components/react/mpx-input.tsx +11 -2
- package/lib/runtime/components/react/mpx-keyboard-avoiding-view.tsx +13 -2
- package/lib/runtime/components/react/mpx-section-list.tsx +439 -0
- package/lib/runtime/components/react/mpx-swiper.tsx +113 -66
- package/lib/runtime/components/react/mpx-web-view.tsx +1 -1
- package/lib/runtime/components/react/tsconfig.json +1 -1
- package/lib/runtime/components/react/utils.tsx +8 -4
- package/lib/runtime/components/web/mpx-scroll-view.vue +5 -2
- package/lib/runtime/components/web/mpx-section-list.vue +551 -0
- package/lib/runtime/components/wx/mpx-section-list-default/list-footer.mpx +26 -0
- package/lib/runtime/components/wx/mpx-section-list-default/list-header.mpx +26 -0
- package/lib/runtime/components/wx/mpx-section-list-default/list-item.mpx +26 -0
- package/lib/runtime/components/wx/mpx-section-list-default/section-header.mpx +26 -0
- package/lib/runtime/components/wx/mpx-section-list.mpx +209 -0
- package/lib/runtime/components/wx/mpx-sticky-header.mpx +40 -0
- package/lib/runtime/components/wx/mpx-sticky-section.mpx +31 -0
- package/lib/template-compiler/compiler.js +7 -3
- package/lib/utils/const.js +29 -0
- package/lib/utils/dom-tag-config.js +1 -1
- package/lib/wxss/loader.js +4 -1
- package/lib/wxss/utils.js +7 -2
- package/package.json +3 -3
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view class="mpx-sticky-header-container">
|
|
3
|
+
<view class="mpx-sticky-header" wx:ref="stickyHeader" wx:style="{{stickyHeaderStyle}}">
|
|
4
|
+
<slot></slot>
|
|
5
|
+
</view>
|
|
6
|
+
<view
|
|
7
|
+
class="mpx-sticky-header-placeholder"
|
|
8
|
+
id="{{stickyId}}"
|
|
9
|
+
wx:style="{{placeholderStyle}}"
|
|
10
|
+
></view>
|
|
11
|
+
</view>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import mpx, { createComponent } from '@mpxjs/core'
|
|
16
|
+
|
|
17
|
+
createComponent({
|
|
18
|
+
properties: {
|
|
19
|
+
offsetTop: {
|
|
20
|
+
type: Number,
|
|
21
|
+
value: 0
|
|
22
|
+
},
|
|
23
|
+
scrollViewId: {
|
|
24
|
+
type: String,
|
|
25
|
+
value: ''
|
|
26
|
+
},
|
|
27
|
+
stickyId: {
|
|
28
|
+
type: String,
|
|
29
|
+
value: ''
|
|
30
|
+
},
|
|
31
|
+
padding: Array,
|
|
32
|
+
enablePolling: {
|
|
33
|
+
type: Boolean,
|
|
34
|
+
value: false
|
|
35
|
+
},
|
|
36
|
+
pollingDuration: {
|
|
37
|
+
type: Number,
|
|
38
|
+
value: 300
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
data: {
|
|
42
|
+
isStickOnTop: false,
|
|
43
|
+
scrollOffsetTop: 0,
|
|
44
|
+
headerHeight: 0,
|
|
45
|
+
stickyHeader: '',
|
|
46
|
+
headerTop: 0,
|
|
47
|
+
lastIntersectionRatio: -1,
|
|
48
|
+
pollingTimer: null
|
|
49
|
+
},
|
|
50
|
+
computed: {
|
|
51
|
+
paddingStyle() {
|
|
52
|
+
if (!this.padding || !Array.isArray(this.padding)) {
|
|
53
|
+
return ''
|
|
54
|
+
}
|
|
55
|
+
const [top = 0, right = 0, bottom = 0, left = 0] = this.padding
|
|
56
|
+
return `padding: ${top}px ${right}px ${bottom}px ${left}px;`
|
|
57
|
+
},
|
|
58
|
+
stickyHeaderStyle() {
|
|
59
|
+
const baseStyle = this.isStickOnTop
|
|
60
|
+
? `position: fixed;top: ${this.scrollOffsetTop + this.offsetTop}px;`
|
|
61
|
+
: ''
|
|
62
|
+
return baseStyle + this.paddingStyle
|
|
63
|
+
},
|
|
64
|
+
placeholderStyle() {
|
|
65
|
+
const position = this.isStickOnTop ? 'relative' : 'absolute'
|
|
66
|
+
return `position: ${position};height: ${this.headerHeight}px;`
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
ready() {
|
|
70
|
+
if (!this.scrollViewId) {
|
|
71
|
+
console.error('[mpx runtime error]: scroll-view-id is necessary property in ali environment')
|
|
72
|
+
}
|
|
73
|
+
if (!this.stickyId) {
|
|
74
|
+
console.error('[mpx runtime error]: sticky-id is necessary property in ali environment')
|
|
75
|
+
}
|
|
76
|
+
this.initStickyHeader()
|
|
77
|
+
// 启动轮询
|
|
78
|
+
if (this.enablePolling) {
|
|
79
|
+
this.startPolling()
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
methods: {
|
|
83
|
+
initStickyHeader() {
|
|
84
|
+
this.createSelectorQuery()
|
|
85
|
+
.select('.mpx-sticky-header')
|
|
86
|
+
.boundingClientRect()
|
|
87
|
+
.exec((rect = []) => {
|
|
88
|
+
this.headerHeight = rect[0]?.height || 0
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
mpx
|
|
92
|
+
.createSelectorQuery()
|
|
93
|
+
.select(`#${this.scrollViewId}`)
|
|
94
|
+
.boundingClientRect()
|
|
95
|
+
.exec((res) => {
|
|
96
|
+
if (!res) return
|
|
97
|
+
this.scrollOffsetTop = res[0]?.top || 0
|
|
98
|
+
this.stickyHeader = mpx.createIntersectionObserver({
|
|
99
|
+
thresholds: [0.1, 0.9]
|
|
100
|
+
})
|
|
101
|
+
this.initObserver()
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
initObserver() {
|
|
105
|
+
this.stickyHeader.relativeTo(`#${this.scrollViewId}`).observe(`#${this.stickyId}`, (res) => {
|
|
106
|
+
const { intersectionRatio, intersectionRect, relativeRect, boundingClientRect } = res
|
|
107
|
+
if (intersectionRatio < this.lastIntersectionRatio) {
|
|
108
|
+
// boundingClientRect.top < relativeRect.top fixed,否则默认布局
|
|
109
|
+
this.handleStickyStateChange(boundingClientRect.top < relativeRect.top)
|
|
110
|
+
} else if (intersectionRatio > this.lastIntersectionRatio) {
|
|
111
|
+
this.handleStickyStateChange(false)
|
|
112
|
+
}
|
|
113
|
+
this.lastIntersectionRatio = intersectionRatio
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
refresh() {
|
|
117
|
+
// 并行执行两个独立的查询
|
|
118
|
+
Promise.all([
|
|
119
|
+
// 查询1:组件内部元素
|
|
120
|
+
new Promise((resolve) => {
|
|
121
|
+
this.createSelectorQuery()
|
|
122
|
+
.select('.mpx-sticky-header')
|
|
123
|
+
.boundingClientRect()
|
|
124
|
+
.select('.mpx-sticky-header-placeholder')
|
|
125
|
+
.boundingClientRect()
|
|
126
|
+
.exec((results) => {
|
|
127
|
+
resolve(results)
|
|
128
|
+
})
|
|
129
|
+
}),
|
|
130
|
+
// 查询2:页面级scrollView
|
|
131
|
+
new Promise((resolve) => {
|
|
132
|
+
mpx.createSelectorQuery()
|
|
133
|
+
.select(`#${this.scrollViewId}`)
|
|
134
|
+
.boundingClientRect()
|
|
135
|
+
.select(`#${this.scrollViewId}`)
|
|
136
|
+
.scrollOffset()
|
|
137
|
+
.exec((results) => {
|
|
138
|
+
resolve(results)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
]).then(([componentResults, scrollResults]) => {
|
|
142
|
+
const [stickyHeaderRect, placeholderRect] = componentResults
|
|
143
|
+
const [scrollViewRect, scrollOffsetData] = scrollResults
|
|
144
|
+
|
|
145
|
+
if (!stickyHeaderRect || !placeholderRect || !scrollViewRect || !scrollOffsetData) return
|
|
146
|
+
|
|
147
|
+
this.scrollOffsetTop = scrollViewRect.top || 0
|
|
148
|
+
this.headerHeight = stickyHeaderRect.height
|
|
149
|
+
|
|
150
|
+
// 模拟 offsetTop 计算:sticky placeholder具体顶部距离 - scrollView顶部 + scrollView滚动位置
|
|
151
|
+
// 必须用 placeholder 到顶部距离,用 sticky 如果刚好差值为 0, 区分不出是本身就在顶部还是 fixed 在顶部
|
|
152
|
+
this.headerTop = placeholderRect.top - this.scrollOffsetTop + scrollOffsetData.scrollTop
|
|
153
|
+
|
|
154
|
+
this.handleStickyStateChange(scrollOffsetData.scrollTop > this.headerTop)
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
handleStickyStateChange(shouldStick) {
|
|
158
|
+
// 如果状态没有变化,直接返回
|
|
159
|
+
if (shouldStick === this.isStickOnTop) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
// 更新状态
|
|
163
|
+
this.isStickOnTop = shouldStick
|
|
164
|
+
// 触发事件
|
|
165
|
+
this.triggerEvent('stickontopchange', {
|
|
166
|
+
isStickOnTop: shouldStick,
|
|
167
|
+
id: this.stickyId
|
|
168
|
+
})
|
|
169
|
+
},
|
|
170
|
+
// 启动轮询
|
|
171
|
+
startPolling() {
|
|
172
|
+
if (this.pollingTimer) {
|
|
173
|
+
clearInterval(this.pollingTimer)
|
|
174
|
+
}
|
|
175
|
+
this.pollingTimer = setInterval(() => {
|
|
176
|
+
this.refresh()
|
|
177
|
+
}, this.pollingDuration)
|
|
178
|
+
},
|
|
179
|
+
// 停止轮询
|
|
180
|
+
stopPolling() {
|
|
181
|
+
if (this.pollingTimer) {
|
|
182
|
+
clearInterval(this.pollingTimer)
|
|
183
|
+
this.pollingTimer = null
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
detached() {
|
|
188
|
+
// 清理观察器
|
|
189
|
+
if (this.stickyHeader) {
|
|
190
|
+
this.stickyHeader.disconnect()
|
|
191
|
+
}
|
|
192
|
+
// 清理轮询定时器
|
|
193
|
+
if (this.pollingTimer) {
|
|
194
|
+
clearInterval(this.pollingTimer)
|
|
195
|
+
this.pollingTimer = null
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
</script>
|
|
200
|
+
<style lang="stylus" scoped>
|
|
201
|
+
.mpx-sticky-header-container
|
|
202
|
+
position relative
|
|
203
|
+
.mpx-sticky-header-placeholder
|
|
204
|
+
position absolute
|
|
205
|
+
left 0
|
|
206
|
+
right 0
|
|
207
|
+
top 0
|
|
208
|
+
pointer-events none
|
|
209
|
+
.mpx-sticky-header
|
|
210
|
+
width 100%
|
|
211
|
+
box-sizing border-box
|
|
212
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view class="mpx-sticky-section">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</view>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import { createComponent } from '@mpxjs/core'
|
|
9
|
+
|
|
10
|
+
createComponent({
|
|
11
|
+
|
|
12
|
+
})
|
|
13
|
+
</script>
|
|
14
|
+
<style lang="stylus" scoped>
|
|
15
|
+
.mpx-sticky-section
|
|
16
|
+
position relative
|
|
17
|
+
</style>
|
|
@@ -101,7 +101,6 @@ export default function useAnimationAPIHooks(props) {
|
|
|
101
101
|
// 动画起始值和终态值类型不一致报错提示一下
|
|
102
102
|
error(`[Mpx runtime error]: Value types of property ${key} must be consistent during the animation`);
|
|
103
103
|
}
|
|
104
|
-
// Todo 对齐wx
|
|
105
104
|
const animation = (toVal === 'auto' && !isNaN(+shareVal)) || (shareVal === 'auto' && !isNaN(+toVal)) ? toVal : getAnimation({ key, value: toVal }, { delay, duration, easing }, needSetCallback ? callback : undefined);
|
|
106
105
|
needSetCallback = false;
|
|
107
106
|
if (!sequence[key]) {
|
|
@@ -63,7 +63,8 @@ const DefaultFallback = ({ onReload }) => {
|
|
|
63
63
|
};
|
|
64
64
|
const DefaultLoading = () => {
|
|
65
65
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
66
|
-
const
|
|
66
|
+
const FastImageModule = require('@d11/react-native-fast-image');
|
|
67
|
+
const FastImage = FastImageModule.default || FastImageModule;
|
|
67
68
|
return (<View style={styles.container}>
|
|
68
69
|
<FastImage style={styles.loadingImage} source={{
|
|
69
70
|
uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif'
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
import { forwardRef, useRef, useState, useContext, useEffect, createElement } from 'react';
|
|
41
41
|
import { TextInput } from 'react-native';
|
|
42
42
|
import { warn } from '@mpxjs/utils';
|
|
43
|
-
import { useUpdateEffect, useTransformStyle, useLayout, extendObject } from './utils';
|
|
43
|
+
import { useUpdateEffect, useTransformStyle, useLayout, extendObject, isAndroid } from './utils';
|
|
44
44
|
import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
45
45
|
import useNodesRef from './useNodesRef';
|
|
46
46
|
import { FormContext, KeyboardAvoidContext } from './context';
|
|
@@ -73,6 +73,8 @@ const Input = forwardRef((props, ref) => {
|
|
|
73
73
|
return '';
|
|
74
74
|
};
|
|
75
75
|
const defaultValue = parseValue(value);
|
|
76
|
+
// 微信小程序的 input 永远是单行,textAlignVertical 固定为 auto
|
|
77
|
+
// multiline 为 true 时表示是 textarea 组件复用此逻辑
|
|
76
78
|
const textAlignVertical = multiline ? 'top' : 'auto';
|
|
77
79
|
const isAutoFocus = !!autoFocus || !!focus;
|
|
78
80
|
const tmpValue = useRef(defaultValue);
|
|
@@ -280,6 +282,11 @@ const Input = forwardRef((props, ref) => {
|
|
|
280
282
|
? nodeRef.current?.focus()
|
|
281
283
|
: nodeRef.current?.blur();
|
|
282
284
|
}, [isAutoFocus]);
|
|
285
|
+
// 使用 multiline 来修复光标位置问题
|
|
286
|
+
// React Native 的 TextInput 在 textAlign center + placeholder 时光标会跑到右边
|
|
287
|
+
// 这个问题只在 Android 上出现
|
|
288
|
+
// 参考:https://github.com/facebook/react-native/issues/28794 (Android only)
|
|
289
|
+
const needMultilineFix = isAndroid && !multiline;
|
|
283
290
|
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
284
291
|
ref: nodeRef,
|
|
285
292
|
style: extendObject({}, normalStyle, layoutStyle),
|
|
@@ -298,7 +305,7 @@ const Input = forwardRef((props, ref) => {
|
|
|
298
305
|
underlineColorAndroid: 'rgba(0,0,0,0)',
|
|
299
306
|
textAlignVertical: textAlignVertical,
|
|
300
307
|
placeholderTextColor: placeholderStyle?.color,
|
|
301
|
-
multiline:
|
|
308
|
+
multiline: multiline || needMultilineFix,
|
|
302
309
|
onTouchStart,
|
|
303
310
|
onTouchEnd,
|
|
304
311
|
onFocus,
|
|
@@ -307,7 +314,7 @@ const Input = forwardRef((props, ref) => {
|
|
|
307
314
|
onSelectionChange,
|
|
308
315
|
onContentSizeChange,
|
|
309
316
|
onSubmitEditing: bindconfirm && onSubmitEditing
|
|
310
|
-
}, !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }), [
|
|
317
|
+
}, needMultilineFix ? { numberOfLines: 1 } : {}, !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }), [
|
|
311
318
|
'type',
|
|
312
319
|
'password',
|
|
313
320
|
'placeholder-style',
|
|
@@ -14,6 +14,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
|
|
|
14
14
|
// 比如机型 iPhone 11 Pro,可能会导致显隐动画冲突
|
|
15
15
|
// 因此增加状态标记 + cancelAnimation 来优化
|
|
16
16
|
const isShow = useRef(false);
|
|
17
|
+
const keybaordHandleTimerRef = useRef(null);
|
|
17
18
|
const animatedStyle = useAnimatedStyle(() => ({
|
|
18
19
|
// translate/position top+ overflow hidden 在 android 上时因为键盘顶起让页面高度变小,同时元素位置上移
|
|
19
20
|
// 此时最底部的区域是超出了页面高度的,hidden生效就被隐藏掉,因此需要 android 配置聚焦时禁用高度缩小
|
|
@@ -62,7 +63,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
|
|
|
62
63
|
// 重置标记位
|
|
63
64
|
keyboardAvoid.current.readyToShow = false;
|
|
64
65
|
}
|
|
65
|
-
if (!keyboardAvoid?.current
|
|
66
|
+
if (!keyboardAvoid?.current) {
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
69
|
isShow.current = true;
|
|
@@ -102,13 +103,23 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
if (isIOS) {
|
|
105
|
-
subscriptions = [
|
|
106
|
+
subscriptions = [
|
|
107
|
+
Keyboard.addListener('keyboardWillShow', (evt) => {
|
|
108
|
+
if (keybaordHandleTimerRef.current) {
|
|
109
|
+
clearTimeout(keybaordHandleTimerRef.current);
|
|
110
|
+
}
|
|
111
|
+
// iphone 在input聚焦时长按滑动后会导致 show 事件先于 focus 事件发生,因此等一下,等 focus 先触发拿到 input,避免键盘出现但input没顶上去
|
|
112
|
+
keybaordHandleTimerRef.current = setTimeout(() => keybaordAvoding(evt), 32);
|
|
113
|
+
}),
|
|
114
|
+
Keyboard.addListener('keyboardWillHide', resetKeyboard)
|
|
115
|
+
];
|
|
106
116
|
}
|
|
107
117
|
else {
|
|
108
118
|
subscriptions = [Keyboard.addListener('keyboardDidShow', keybaordAvoding), Keyboard.addListener('keyboardDidHide', resetKeyboard)];
|
|
109
119
|
}
|
|
110
120
|
return () => {
|
|
111
121
|
subscriptions.forEach(subscription => subscription.remove());
|
|
122
|
+
keybaordHandleTimerRef.current && clearTimeout(keybaordHandleTimerRef.current);
|
|
112
123
|
};
|
|
113
124
|
}, [keyboardAvoid]);
|
|
114
125
|
return (<View style={style} onTouchEnd={onTouchEnd} onTouchMove={onTouchEnd}>
|
|
@@ -126,9 +126,11 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
126
126
|
const preAbsolutePos = useSharedValue(0);
|
|
127
127
|
// 记录从onBegin 到 onTouchesUp 时移动的距离
|
|
128
128
|
const moveTranstion = useSharedValue(0);
|
|
129
|
+
// 记录用户手滑动的方向
|
|
130
|
+
const moveDir = useSharedValue(0);
|
|
129
131
|
const timerId = useRef(0);
|
|
130
132
|
const intervalTimer = props.interval || 500;
|
|
131
|
-
//
|
|
133
|
+
// 记录是否首次,首次不能触发bindchange回调
|
|
132
134
|
const isFirstRef = useRef(true);
|
|
133
135
|
const simultaneousHandlers = flatGesture(originSimultaneousHandlers);
|
|
134
136
|
const waitForHandlers = flatGesture(waitFor);
|
|
@@ -356,7 +358,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
356
358
|
resumeLoop
|
|
357
359
|
};
|
|
358
360
|
}, []);
|
|
359
|
-
function handleSwiperChange(current
|
|
361
|
+
function handleSwiperChange(current) {
|
|
360
362
|
const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef });
|
|
361
363
|
bindchange && bindchange(eventData);
|
|
362
364
|
}
|
|
@@ -465,6 +467,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
465
467
|
}
|
|
466
468
|
}, [circular, patchElmNum, displayMultipleItems]);
|
|
467
469
|
const { gestureHandler } = useMemo(() => {
|
|
470
|
+
// 基于transdir + 当前offset计算索引
|
|
468
471
|
function getTargetPosition(eventData) {
|
|
469
472
|
'worklet';
|
|
470
473
|
// 移动的距离
|
|
@@ -578,10 +581,8 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
578
581
|
}
|
|
579
582
|
});
|
|
580
583
|
}
|
|
581
|
-
|
|
582
|
-
function computeHalf(eventData) {
|
|
584
|
+
function computeHalf() {
|
|
583
585
|
'worklet';
|
|
584
|
-
const { transdir } = eventData;
|
|
585
586
|
const currentOffset = Math.abs(offset.value);
|
|
586
587
|
let preOffset = (currentIndex.value + patchElmNumShared.value) * step.value;
|
|
587
588
|
if (circularShared.value) {
|
|
@@ -590,35 +591,14 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
590
591
|
// 正常事件中拿到的translation值(正向滑动<0,倒着滑>0)
|
|
591
592
|
const diffOffset = preOffset - currentOffset;
|
|
592
593
|
const half = Math.abs(diffOffset) > step.value / 2;
|
|
593
|
-
|
|
594
|
-
return {
|
|
595
|
-
diffOffset,
|
|
596
|
-
half,
|
|
597
|
-
isTriggerUpdateHalf
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
function handleLongPress(eventData) {
|
|
601
|
-
'worklet';
|
|
602
|
-
const { diffOffset, half, isTriggerUpdateHalf } = computeHalf(eventData);
|
|
603
|
-
if (+diffOffset === 0) {
|
|
604
|
-
runOnJS(runOnJSCallback)('resumeLoop');
|
|
605
|
-
}
|
|
606
|
-
else if (isTriggerUpdateHalf) {
|
|
607
|
-
// 如果触发了onUpdate时的索引变更
|
|
608
|
-
handleEnd(eventData);
|
|
609
|
-
}
|
|
610
|
-
else if (half) {
|
|
611
|
-
handleEnd(eventData);
|
|
612
|
-
}
|
|
613
|
-
else {
|
|
614
|
-
handleBack(eventData);
|
|
615
|
-
}
|
|
594
|
+
return half;
|
|
616
595
|
}
|
|
617
596
|
function reachBoundary(eventData) {
|
|
618
597
|
'worklet';
|
|
619
598
|
// 1. 基于当前的offset和translation判断是否超过当前边界值
|
|
620
599
|
const { translation } = eventData;
|
|
621
|
-
|
|
600
|
+
// 与终点的逻辑对齐,都是超过补位元素对应的起点offset
|
|
601
|
+
const boundaryStart = 0;
|
|
622
602
|
const boundaryEnd = -(childrenLength.value + patchElmNumShared.value) * step.value;
|
|
623
603
|
const moveToOffset = offset.value + translation;
|
|
624
604
|
let isBoundary = false;
|
|
@@ -635,7 +615,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
635
615
|
// 超过边界的距离
|
|
636
616
|
const exceedLength = Math.abs(boundaryStart) - Math.abs(moveToOffset);
|
|
637
617
|
// 计算对标正常元素所在的offset
|
|
638
|
-
resetOffset = (patchElmNumShared.value + childrenLength.value - 1) * step.value
|
|
618
|
+
resetOffset = (patchElmNumShared.value + childrenLength.value - 1) * step.value - exceedLength;
|
|
639
619
|
}
|
|
640
620
|
return {
|
|
641
621
|
isBoundary,
|
|
@@ -674,6 +654,14 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
674
654
|
}
|
|
675
655
|
return finalOffset;
|
|
676
656
|
}
|
|
657
|
+
// 设置手势移动的方向
|
|
658
|
+
function setMoveDir(curAbsoPos) {
|
|
659
|
+
'worklet';
|
|
660
|
+
const distance = curAbsoPos - preAbsolutePos.value;
|
|
661
|
+
if (distance) {
|
|
662
|
+
moveDir.value = curAbsoPos - preAbsolutePos.value;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
677
665
|
const gesturePan = Gesture.Pan()
|
|
678
666
|
.onBegin((e) => {
|
|
679
667
|
'worklet';
|
|
@@ -695,9 +683,9 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
695
683
|
transdir: moveDistance
|
|
696
684
|
};
|
|
697
685
|
// 1. 支持滑动中超出一半更新索引的能力:只更新索引并不会影响onFinalize依据当前offset计算的索引
|
|
698
|
-
const
|
|
699
|
-
if (childrenLength.value > 1 &&
|
|
700
|
-
const { selectedIndex } = getTargetPosition(
|
|
686
|
+
const offsetHalf = computeHalf();
|
|
687
|
+
if (childrenLength.value > 1 && offsetHalf) {
|
|
688
|
+
const { selectedIndex } = getTargetPosition({ transdir: moveDistance });
|
|
701
689
|
currentIndex.value = selectedIndex;
|
|
702
690
|
}
|
|
703
691
|
// 2. 非循环: 处理用户一直拖拽到临界点的场景,如果放到onFinalize无法阻止offset.value更新为越界的值
|
|
@@ -709,6 +697,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
709
697
|
const finalOffset = handleResistanceMove(eventData);
|
|
710
698
|
offset.value = finalOffset;
|
|
711
699
|
}
|
|
700
|
+
setMoveDir(e[strAbso]);
|
|
712
701
|
preAbsolutePos.value = e[strAbso];
|
|
713
702
|
return;
|
|
714
703
|
}
|
|
@@ -716,6 +705,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
716
705
|
if (circularShared.value && childrenLength.value === 1) {
|
|
717
706
|
const finalOffset = handleResistanceMove(eventData);
|
|
718
707
|
offset.value = finalOffset;
|
|
708
|
+
setMoveDir(e[strAbso]);
|
|
719
709
|
preAbsolutePos.value = e[strAbso];
|
|
720
710
|
return;
|
|
721
711
|
}
|
|
@@ -727,6 +717,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
727
717
|
else {
|
|
728
718
|
offset.value = moveDistance + offset.value;
|
|
729
719
|
}
|
|
720
|
+
setMoveDir(e[strAbso]);
|
|
730
721
|
preAbsolutePos.value = e[strAbso];
|
|
731
722
|
})
|
|
732
723
|
.onFinalize((e) => {
|
|
@@ -735,10 +726,21 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
735
726
|
return;
|
|
736
727
|
touchfinish.value = true;
|
|
737
728
|
// 触发过onUpdate正常情况下e[strAbso] - preAbsolutePos.value=0; 未触发过onUpdate的情况下e[strAbso] - preAbsolutePos.value 不为0
|
|
729
|
+
// 正常状态下基于onUpdate时的moveDir判断方向、未触发onUpdate的则基于onBegin的moveTranstion判断方向
|
|
738
730
|
const moveDistance = e[strAbso] - preAbsolutePos.value;
|
|
731
|
+
// 默认兜底方向: 以onBegin为起点,因一些原因未触发onUpdate但是触发了位移
|
|
732
|
+
const defaultDir = e[strAbso] - moveTranstion.value;
|
|
733
|
+
// 实时方向:方向基于onUpdate时的方向,滑动的速度超过阈值时基于实时的滑动方向计算
|
|
734
|
+
const realtimeData = {
|
|
735
|
+
transdir: moveDir.value || defaultDir
|
|
736
|
+
};
|
|
737
|
+
// 起始方向:基于用户起始手势
|
|
738
|
+
const originData = {
|
|
739
|
+
transdir: defaultDir
|
|
740
|
+
};
|
|
739
741
|
const eventData = {
|
|
740
742
|
translation: moveDistance,
|
|
741
|
-
transdir:
|
|
743
|
+
transdir: realtimeData.transdir
|
|
742
744
|
};
|
|
743
745
|
// 1. 只有一个元素:循环 和 非循环状态,都走回弹效果
|
|
744
746
|
if (childrenLength.value === 1) {
|
|
@@ -752,21 +754,39 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
752
754
|
// 非循环支持最后元素可滑动能力后,向左快速移动未超过最大可移动范围一半,因为offset为正值,向左滑动handleBack,默认向上取整
|
|
753
755
|
// 但是在offset大于0时,取0。[-100, 0](back取0), [0, 100](back取1), 所以handleLongPress里的处理逻辑需要兼容支持,因此这里直接单独处理,不耦合下方公共的判断逻辑。
|
|
754
756
|
if (!circularShared.value && !canMove(eventData)) {
|
|
755
|
-
if (
|
|
756
|
-
handleBack(
|
|
757
|
+
if (realtimeData.transdir < 0) {
|
|
758
|
+
handleBack(realtimeData);
|
|
757
759
|
}
|
|
758
760
|
else {
|
|
759
|
-
handleEnd(
|
|
761
|
+
handleEnd(realtimeData);
|
|
760
762
|
}
|
|
761
763
|
return;
|
|
762
764
|
}
|
|
763
765
|
// 3. 非循环状态可移动态、循环状态, 正常逻辑处理
|
|
764
766
|
const velocity = e[strVelocity];
|
|
765
|
-
|
|
766
|
-
|
|
767
|
+
// 用于判断是否超过一半,基于索引判断是否超过一半不可行(1.滑动过程中索引会变更导致计算反向, 2.边界场景会更新offset也会导致基于索引+offset判断实效)
|
|
768
|
+
const tmp = offset.value % step.value > step.value / 2;
|
|
769
|
+
// 小于0手向左滑动
|
|
770
|
+
const offsetHalf = originData.transdir < 0 ? tmp : !tmp;
|
|
771
|
+
if (offsetHalf) {
|
|
772
|
+
if (Math.abs(velocity) > longPressRatio) {
|
|
773
|
+
// 超过速度阈值,按照实时方向(快速来回滑动)
|
|
774
|
+
handleEnd(realtimeData);
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
// 超过速度阈值,按照起始方向(慢速长按)
|
|
778
|
+
handleEnd(originData);
|
|
779
|
+
}
|
|
767
780
|
}
|
|
768
781
|
else {
|
|
769
|
-
|
|
782
|
+
if (Math.abs(velocity) > longPressRatio) {
|
|
783
|
+
// 超过速度阈值,按照实时方向(快速来回滑动)
|
|
784
|
+
handleEnd(realtimeData);
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
// 超过速度阈值,按照起始方向(慢速长按)
|
|
788
|
+
handleBack(originData);
|
|
789
|
+
}
|
|
770
790
|
}
|
|
771
791
|
})
|
|
772
792
|
.withRef(swiperGestureRef);
|
|
@@ -487,7 +487,7 @@ export function useTransformStyle(styleObj = {}, { enableVar, transformRadiusPer
|
|
|
487
487
|
// transform rpx to px
|
|
488
488
|
transformBoxShadow(normalStyle);
|
|
489
489
|
// transform 字符串格式转化数组格式(先转数组再处理css var)
|
|
490
|
-
transformTransform(
|
|
490
|
+
transformTransform(normalStyle);
|
|
491
491
|
return {
|
|
492
492
|
hasVarDec,
|
|
493
493
|
varContextRef,
|
|
@@ -637,9 +637,13 @@ export function getCurrentPage(pageId) {
|
|
|
637
637
|
const pages = global.getCurrentPages();
|
|
638
638
|
return pages.find((page) => isFunction(page.getPageId) && page.getPageId() === pageId);
|
|
639
639
|
}
|
|
640
|
-
export function renderImage(imageProps, enableFastImage =
|
|
641
|
-
|
|
642
|
-
|
|
640
|
+
export function renderImage(imageProps, enableFastImage = true) {
|
|
641
|
+
let Component = Image;
|
|
642
|
+
if (enableFastImage) {
|
|
643
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
644
|
+
const fastImageModule = require('@d11/react-native-fast-image');
|
|
645
|
+
Component = fastImageModule.default || fastImageModule;
|
|
646
|
+
}
|
|
643
647
|
return createElement(Component, imageProps);
|
|
644
648
|
}
|
|
645
649
|
export function pickStyle(styleObj = {}, pickedKeys, callback) {
|
|
@@ -82,7 +82,8 @@ const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
|
|
|
82
82
|
|
|
83
83
|
const DefaultLoading = () => {
|
|
84
84
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
85
|
-
const
|
|
85
|
+
const FastImageModule = require('@d11/react-native-fast-image')
|
|
86
|
+
const FastImage = FastImageModule.default || FastImageModule
|
|
86
87
|
return (
|
|
87
88
|
<View style={styles.container}>
|
|
88
89
|
<FastImage
|