@mpxjs/webpack-plugin 2.10.15-4 → 2.10.15-prelease.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/dependencies/DynamicEntryDependency.js +1 -1
- package/lib/dependencies/ImportDependency.js +102 -0
- package/lib/{retry-runtime-module.js → dependencies/RetryRuntimeModule.js} +1 -1
- package/lib/index.js +13 -15
- package/lib/platform/template/wx/component-config/progress.js +12 -0
- package/lib/platform/template/wx/component-config/slider.js +12 -0
- package/lib/platform/template/wx/component-config/unsupported.js +1 -1
- package/lib/platform/template/wx/index.js +3 -1
- package/lib/resolver/AddEnvPlugin.js +13 -0
- package/lib/resolver/AddModePlugin.js +18 -0
- package/lib/runtime/components/react/dist/getInnerListeners.js +35 -21
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +102 -34
- package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +3 -5
- package/lib/runtime/components/react/dist/mpx-progress.jsx +163 -0
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +51 -9
- package/lib/runtime/components/react/dist/mpx-slider.jsx +321 -0
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +9 -16
- package/lib/runtime/components/react/dist/mpx-view.jsx +7 -10
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +20 -1
- package/lib/runtime/components/react/getInnerListeners.ts +41 -22
- package/lib/runtime/components/react/mpx-movable-view.tsx +156 -48
- package/lib/runtime/components/react/mpx-portal/portal-manager.tsx +4 -8
- package/lib/runtime/components/react/mpx-progress.tsx +259 -0
- package/lib/runtime/components/react/mpx-scroll-view.tsx +54 -9
- package/lib/runtime/components/react/mpx-slider.tsx +444 -0
- package/lib/runtime/components/react/mpx-swiper.tsx +9 -16
- package/lib/runtime/components/react/mpx-view.tsx +7 -10
- package/lib/runtime/components/react/mpx-web-view.tsx +22 -1
- package/lib/runtime/components/react/types/getInnerListeners.d.ts +7 -2
- package/lib/runtime/components/web/mpx-input.vue +14 -0
- package/lib/runtime/components/web/mpx-movable-area.vue +43 -19
- package/lib/runtime/components/web/mpx-movable-view.vue +93 -3
- package/lib/runtime/components/web/mpx-scroll-view.vue +7 -1
- package/lib/runtime/components/web/mpx-swiper.vue +1 -2
- package/lib/runtime/components/web/mpx-video.vue +12 -1
- package/lib/runtime/components/web/mpx-web-view.vue +3 -3
- package/lib/runtime/optionProcessor.js +3 -1
- package/lib/runtime/optionProcessorReact.js +4 -2
- package/lib/template-compiler/compiler.js +69 -35
- package/lib/utils/chain-assign.js +47 -0
- package/lib/utils/check-core-version-match.js +75 -15
- package/lib/wxs/pre-loader.js +5 -5
- package/lib/wxss/utils.js +1 -1
- package/package.json +3 -2
- package/lib/dependencies/ImportDependencyTemplate.js +0 -50
|
@@ -120,8 +120,6 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
120
120
|
const strVelocity = 'velocity' + dir.toUpperCase();
|
|
121
121
|
// 标识手指触摸和抬起, 起点在onBegin
|
|
122
122
|
const touchfinish = useSharedValue(true);
|
|
123
|
-
// 记录onUpdate时的方向,用于进行onFinalize中的值修正
|
|
124
|
-
const preUpdateTransDir = useSharedValue(0);
|
|
125
123
|
// 记录上一帧的绝对定位坐标
|
|
126
124
|
const preAbsolutePos = useSharedValue(0);
|
|
127
125
|
// 记录从onBegin 到 onTouchesUp 时移动的距离
|
|
@@ -327,9 +325,11 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
327
325
|
resumeLoop
|
|
328
326
|
};
|
|
329
327
|
}, []);
|
|
330
|
-
function handleSwiperChange(current) {
|
|
331
|
-
|
|
332
|
-
|
|
328
|
+
function handleSwiperChange(current, pCurrent) {
|
|
329
|
+
if (pCurrent !== currentIndex.value) {
|
|
330
|
+
const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef });
|
|
331
|
+
bindchange && bindchange(eventData);
|
|
332
|
+
}
|
|
333
333
|
}
|
|
334
334
|
const runOnJSCallbackRef = useRef({
|
|
335
335
|
loop,
|
|
@@ -379,7 +379,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
379
379
|
// 1. 用户在当前页切换选中项,动画;用户携带选中index打开到swiper页直接选中不走动画
|
|
380
380
|
useAnimatedReaction(() => currentIndex.value, (newIndex, preIndex) => {
|
|
381
381
|
// 这里必须传递函数名, 直接写()=> {}形式会报 访问了未sharedValue信息
|
|
382
|
-
if (newIndex !== preIndex &&
|
|
382
|
+
if (newIndex !== preIndex && bindchange) {
|
|
383
383
|
runOnJS(runOnJSCallback)('handleSwiperChange', newIndex, propCurrent);
|
|
384
384
|
}
|
|
385
385
|
});
|
|
@@ -411,9 +411,9 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
411
411
|
}
|
|
412
412
|
}, [children.length]);
|
|
413
413
|
useEffect(() => {
|
|
414
|
-
// 1. 如果用户在touch的过程中, 外部更新了current
|
|
414
|
+
// 1. 如果用户在touch的过程中, 外部更新了current以外部为准(小程序表现)
|
|
415
415
|
// 2. 手指滑动过程中更新索引,外部会把current再传入进来,导致offset直接更新,增加判断不同才更新
|
|
416
|
-
if (propCurrent !== currentIndex.value
|
|
416
|
+
if (propCurrent !== currentIndex.value) {
|
|
417
417
|
updateCurrent(propCurrent, step.value);
|
|
418
418
|
}
|
|
419
419
|
}, [propCurrent]);
|
|
@@ -662,7 +662,6 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
662
662
|
translation: moveDistance,
|
|
663
663
|
transdir: moveDistance
|
|
664
664
|
};
|
|
665
|
-
preUpdateTransDir.value = moveDistance;
|
|
666
665
|
// 1. 支持滑动中超出一半更新索引的能力:只更新索引并不会影响onFinalize依据当前offset计算的索引
|
|
667
666
|
const { half } = computeHalf(eventData);
|
|
668
667
|
if (childrenLength.value > 1 && half) {
|
|
@@ -703,17 +702,11 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
703
702
|
if (touchfinish.value)
|
|
704
703
|
return;
|
|
705
704
|
touchfinish.value = true;
|
|
706
|
-
/**
|
|
707
|
-
* 安卓修正
|
|
708
|
-
* 问题:部分安卓机型onFinalize中拿到的absoluteX 有问题
|
|
709
|
-
* 案例:比如手指从右向左滑的时候,onUpdate拿到的是241.64346313476562, 而onFinalize中拿到的是241.81817626953125,理论上onFinalize中应该比onUpdate小才对吧
|
|
710
|
-
* 解决方式:修正
|
|
711
|
-
*/
|
|
712
705
|
// 触发过onUpdate正常情况下e[strAbso] - preAbsolutePos.value=0; 未触发过onUpdate的情况下e[strAbso] - preAbsolutePos.value 不为0
|
|
713
706
|
const moveDistance = e[strAbso] - preAbsolutePos.value;
|
|
714
707
|
const eventData = {
|
|
715
708
|
translation: moveDistance,
|
|
716
|
-
transdir:
|
|
709
|
+
transdir: moveDistance !== 0 ? moveDistance : e[strAbso] - moveTranstion.value
|
|
717
710
|
};
|
|
718
711
|
// 1. 只有一个元素:循环 和 非循环状态,都走回弹效果
|
|
719
712
|
if (childrenLength.value === 1) {
|
|
@@ -203,16 +203,13 @@ function backgroundSize(imageProps, preImageInfo, imageSize, layoutInfo) {
|
|
|
203
203
|
else { // 数值类型 ImageStyle
|
|
204
204
|
// 数值类型设置为 stretch
|
|
205
205
|
imageProps.resizeMode = 'stretch';
|
|
206
|
-
if (type === 'linear') {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
height: dimensionHeight
|
|
214
|
-
};
|
|
215
|
-
}
|
|
206
|
+
if (type === 'linear' && (!layoutWidth || !layoutHeight)) {
|
|
207
|
+
// ios 上 linear 组件只要重新触发渲染,在渲染过程中外层容器 width 或者 height 被设置为 0,通过设置 % 的方式会渲染不出来,即使后面再更新为正常宽高也渲染不出来
|
|
208
|
+
// 所以 hack 手动先将 linear 宽高也设置为 0,后面再更新为正确的数值或 %。
|
|
209
|
+
dimensions = {
|
|
210
|
+
width: 0,
|
|
211
|
+
height: 0
|
|
212
|
+
};
|
|
216
213
|
}
|
|
217
214
|
else {
|
|
218
215
|
dimensions = {
|
|
@@ -80,9 +80,25 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
80
80
|
useNodesRef(props, ref, webViewRef, {
|
|
81
81
|
style: defaultWebViewStyle
|
|
82
82
|
});
|
|
83
|
+
const hostValidate = (url) => {
|
|
84
|
+
const host = url && new URL(url).host;
|
|
85
|
+
const hostWhitelists = mpx.config.rnConfig?.webviewConfig?.hostWhitelists || [];
|
|
86
|
+
if (hostWhitelists.length) {
|
|
87
|
+
return hostWhitelists.some((item) => {
|
|
88
|
+
return host.endsWith(item);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
83
95
|
if (!src) {
|
|
84
96
|
return null;
|
|
85
97
|
}
|
|
98
|
+
if (!hostValidate(src)) {
|
|
99
|
+
console.error('访问页面域名不符合domainWhiteLists白名单配置,请确认是否正确配置该域名白名单');
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
86
102
|
const _reload = function () {
|
|
87
103
|
if (__mpx_mode__ !== 'ios') {
|
|
88
104
|
fristLoaded.current = false; // 安卓需要重新设置
|
|
@@ -133,6 +149,9 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
133
149
|
}
|
|
134
150
|
};
|
|
135
151
|
const _message = function (res) {
|
|
152
|
+
if (!hostValidate(res.nativeEvent?.url)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
136
155
|
let data = {};
|
|
137
156
|
let asyncCallback;
|
|
138
157
|
const navObj = promisify({ redirectTo, navigateTo, navigateBack, reLaunch, switchTab });
|
|
@@ -184,7 +203,7 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
184
203
|
break;
|
|
185
204
|
default:
|
|
186
205
|
if (type) {
|
|
187
|
-
const implement = mpx.config.webviewConfig.apiImplementations && mpx.config.webviewConfig.apiImplementations[type];
|
|
206
|
+
const implement = mpx.config.rnConfig.webviewConfig && mpx.config.rnConfig.webviewConfig.apiImplementations && mpx.config.rnConfig.webviewConfig.apiImplementations[type];
|
|
188
207
|
if (isFunction(implement)) {
|
|
189
208
|
asyncCallback = Promise.resolve(implement(...params));
|
|
190
209
|
}
|
|
@@ -10,11 +10,13 @@ import {
|
|
|
10
10
|
RemoveProps,
|
|
11
11
|
InnerRef,
|
|
12
12
|
LayoutRef,
|
|
13
|
-
ExtendedNativeTouchEvent
|
|
13
|
+
ExtendedNativeTouchEvent,
|
|
14
|
+
GlobalEventState
|
|
14
15
|
} from './types/getInnerListeners'
|
|
15
16
|
|
|
16
|
-
const globalEventState = {
|
|
17
|
-
needPress: true
|
|
17
|
+
const globalEventState: GlobalEventState = {
|
|
18
|
+
needPress: true,
|
|
19
|
+
identifier: null
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
const getTouchEvent = (
|
|
@@ -137,39 +139,54 @@ function checkIsNeedPress (e: ExtendedNativeTouchEvent, type: 'bubble' | 'captur
|
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
function shouldHandleTapEvent (e: ExtendedNativeTouchEvent, eventConfig: EventConfig) {
|
|
143
|
+
const { identifier } = e.nativeEvent.changedTouches[0]
|
|
144
|
+
return eventConfig.tap && globalEventState.identifier === identifier
|
|
145
|
+
}
|
|
146
|
+
|
|
140
147
|
function handleTouchstart (e: ExtendedNativeTouchEvent, type: EventType, eventConfig: EventConfig) {
|
|
141
|
-
// 阻止事件被释放放回对象池,导致对象复用 _stoppedEventTypes 状态被保留
|
|
142
148
|
e.persist()
|
|
143
149
|
const { innerRef } = eventConfig
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
const touch = e.nativeEvent.changedTouches[0]
|
|
151
|
+
const { identifier } = touch
|
|
152
|
+
|
|
153
|
+
const isSingle = e.nativeEvent.touches.length <= 1
|
|
154
|
+
|
|
155
|
+
if (isSingle) {
|
|
156
|
+
// 仅在 touchstart 记录第一个单指触摸点
|
|
157
|
+
globalEventState.identifier = identifier
|
|
158
|
+
globalEventState.needPress = true
|
|
159
|
+
innerRef.current.mpxPressInfo.detail = {
|
|
160
|
+
x: touch.pageX,
|
|
161
|
+
y: touch.pageY
|
|
162
|
+
}
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
handleEmitEvent('touchstart', e, type, eventConfig)
|
|
151
166
|
|
|
152
167
|
if (eventConfig.longpress) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
// 只有单指触摸时才启动长按定时器
|
|
169
|
+
if (isSingle) {
|
|
170
|
+
if (e._stoppedEventTypes?.has('longpress')) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
if (eventConfig.longpress.hasCatch) {
|
|
174
|
+
e._stoppedEventTypes = e._stoppedEventTypes || new Set()
|
|
175
|
+
e._stoppedEventTypes.add('longpress')
|
|
176
|
+
}
|
|
177
|
+
innerRef.current.startTimer[type] && clearTimeout(innerRef.current.startTimer[type] as unknown as number)
|
|
178
|
+
innerRef.current.startTimer[type] = setTimeout(() => {
|
|
179
|
+
globalEventState.needPress = false
|
|
180
|
+
handleEmitEvent('longpress', e, type, eventConfig)
|
|
181
|
+
}, 350)
|
|
159
182
|
}
|
|
160
|
-
innerRef.current.startTimer[type] && clearTimeout(innerRef.current.startTimer[type] as unknown as number)
|
|
161
|
-
innerRef.current.startTimer[type] = setTimeout(() => {
|
|
162
|
-
// 只要触发过longpress, 全局就不再触发tap
|
|
163
|
-
globalEventState.needPress = false
|
|
164
|
-
handleEmitEvent('longpress', e, type, eventConfig)
|
|
165
|
-
}, 350)
|
|
166
183
|
}
|
|
167
184
|
}
|
|
168
185
|
|
|
169
186
|
function handleTouchmove (e: ExtendedNativeTouchEvent, type: EventType, eventConfig: EventConfig) {
|
|
170
187
|
const { innerRef } = eventConfig
|
|
171
188
|
handleEmitEvent('touchmove', e, type, eventConfig)
|
|
172
|
-
if (eventConfig
|
|
189
|
+
if (shouldHandleTapEvent(e, eventConfig)) {
|
|
173
190
|
checkIsNeedPress(e, type, innerRef)
|
|
174
191
|
}
|
|
175
192
|
}
|
|
@@ -178,7 +195,9 @@ function handleTouchend (e: ExtendedNativeTouchEvent, type: EventType, eventConf
|
|
|
178
195
|
const { innerRef, disableTap } = eventConfig
|
|
179
196
|
handleEmitEvent('touchend', e, type, eventConfig)
|
|
180
197
|
innerRef.current.startTimer[type] && clearTimeout(innerRef.current.startTimer[type] as unknown as number)
|
|
181
|
-
|
|
198
|
+
|
|
199
|
+
// 只有单指触摸结束时才触发 tap
|
|
200
|
+
if (shouldHandleTapEvent(e, eventConfig)) {
|
|
182
201
|
checkIsNeedPress(e, type, innerRef)
|
|
183
202
|
if (!globalEventState.needPress || (type === 'bubble' && disableTap) || e._stoppedEventTypes?.has('tap')) {
|
|
184
203
|
return
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* ✔ out-of-bounds
|
|
5
5
|
* ✔ x
|
|
6
6
|
* ✔ y
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* ✔ damping
|
|
8
|
+
* ✔ friction
|
|
9
9
|
* ✔ disabled
|
|
10
10
|
* ✘ scale
|
|
11
11
|
* ✘ scale-min
|
|
@@ -27,13 +27,125 @@ import { GestureDetector, Gesture, GestureTouchEvent, GestureStateChangeEvent, P
|
|
|
27
27
|
import Animated, {
|
|
28
28
|
useSharedValue,
|
|
29
29
|
useAnimatedStyle,
|
|
30
|
-
withDecay,
|
|
31
30
|
runOnJS,
|
|
32
31
|
runOnUI,
|
|
33
|
-
|
|
32
|
+
withTiming,
|
|
33
|
+
Easing
|
|
34
34
|
} from 'react-native-reanimated'
|
|
35
35
|
import { collectDataset, noop } from '@mpxjs/utils'
|
|
36
36
|
|
|
37
|
+
// 超出边界处理函数,参考微信小程序的超出边界衰减效果
|
|
38
|
+
const applyBoundaryDecline = (
|
|
39
|
+
newValue: number,
|
|
40
|
+
range: [min: number, max: number]
|
|
41
|
+
): number => {
|
|
42
|
+
'worklet'
|
|
43
|
+
|
|
44
|
+
const decline = (distance: number): number => {
|
|
45
|
+
'worklet'
|
|
46
|
+
return Math.sqrt(Math.abs(distance))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (newValue < range[0]) {
|
|
50
|
+
const overDistance = range[0] - newValue
|
|
51
|
+
return range[0] - decline(overDistance)
|
|
52
|
+
} else if (newValue > range[1]) {
|
|
53
|
+
const overDistance = newValue - range[1]
|
|
54
|
+
return range[1] + decline(overDistance)
|
|
55
|
+
}
|
|
56
|
+
return newValue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 参考微信小程序的弹簧阻尼系统实现
|
|
60
|
+
const withWechatSpring = (
|
|
61
|
+
toValue: number,
|
|
62
|
+
dampingParam = 20,
|
|
63
|
+
callback?: () => void
|
|
64
|
+
) => {
|
|
65
|
+
'worklet'
|
|
66
|
+
|
|
67
|
+
// 弹簧参数计算
|
|
68
|
+
const m = 1 // 质量
|
|
69
|
+
const k = 9 * Math.pow(dampingParam, 2) / 40 // 弹簧系数
|
|
70
|
+
const c = dampingParam // 阻尼系数
|
|
71
|
+
|
|
72
|
+
// 判别式:r = c² - 4mk
|
|
73
|
+
const discriminant = c * c - 4 * m * k
|
|
74
|
+
|
|
75
|
+
// 计算动画持续时间和缓动函数
|
|
76
|
+
let duration: number
|
|
77
|
+
let easingFunction: any
|
|
78
|
+
|
|
79
|
+
if (Math.abs(discriminant) < 0.01) {
|
|
80
|
+
// 临界阻尼 (discriminant ≈ 0)
|
|
81
|
+
// 使用cubic-out模拟临界阻尼的平滑过渡
|
|
82
|
+
duration = Math.max(350, Math.min(800, 2000 / dampingParam))
|
|
83
|
+
easingFunction = Easing.out(Easing.cubic)
|
|
84
|
+
} else if (discriminant > 0) {
|
|
85
|
+
// 过阻尼 (discriminant > 0)
|
|
86
|
+
// 使用指数缓动模拟过阻尼的缓慢收敛
|
|
87
|
+
duration = Math.max(450, Math.min(1000, 2500 / dampingParam))
|
|
88
|
+
easingFunction = Easing.out(Easing.exp)
|
|
89
|
+
} else {
|
|
90
|
+
// 欠阻尼 (discriminant < 0) - 会产生振荡
|
|
91
|
+
// 计算振荡频率和衰减率
|
|
92
|
+
const dampingRatio = c / (2 * Math.sqrt(m * k)) // 阻尼比
|
|
93
|
+
|
|
94
|
+
// 根据阻尼比调整动画参数
|
|
95
|
+
if (dampingRatio < 0.7) {
|
|
96
|
+
// 明显振荡
|
|
97
|
+
duration = Math.max(600, Math.min(1200, 3000 / dampingParam))
|
|
98
|
+
// 创建带振荡的贝塞尔曲线
|
|
99
|
+
easingFunction = Easing.bezier(0.175, 0.885, 0.32, 1.275)
|
|
100
|
+
} else {
|
|
101
|
+
// 轻微振荡
|
|
102
|
+
duration = Math.max(400, Math.min(800, 2000 / dampingParam))
|
|
103
|
+
easingFunction = Easing.bezier(0.25, 0.46, 0.45, 0.94)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return withTiming(toValue, {
|
|
108
|
+
duration,
|
|
109
|
+
easing: easingFunction
|
|
110
|
+
}, callback)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 参考微信小程序friction的惯性动画
|
|
114
|
+
const withWechatDecay = (
|
|
115
|
+
velocity: number,
|
|
116
|
+
currentPosition: number,
|
|
117
|
+
clampRange: [min: number, max: number],
|
|
118
|
+
frictionValue = 2,
|
|
119
|
+
callback?: () => void
|
|
120
|
+
) => {
|
|
121
|
+
'worklet'
|
|
122
|
+
|
|
123
|
+
// 微信小程序friction算法: delta = -1.5 * v² / a, 其中 a = -f * v / |v|
|
|
124
|
+
// 如果friction小于等于0,设置为默认值2
|
|
125
|
+
const validFriction = frictionValue <= 0 ? 2 : frictionValue
|
|
126
|
+
const f = 1000 * validFriction
|
|
127
|
+
const acceleration = velocity !== 0 ? -f * velocity / Math.abs(velocity) : 0
|
|
128
|
+
const delta = acceleration !== 0 ? (-1.5 * velocity * velocity) / acceleration : 0
|
|
129
|
+
|
|
130
|
+
let finalPosition = currentPosition + delta
|
|
131
|
+
|
|
132
|
+
// 边界限制
|
|
133
|
+
if (finalPosition < clampRange[0]) {
|
|
134
|
+
finalPosition = clampRange[0]
|
|
135
|
+
} else if (finalPosition > clampRange[1]) {
|
|
136
|
+
finalPosition = clampRange[1]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 计算动画时长
|
|
140
|
+
const distance = Math.abs(finalPosition - currentPosition)
|
|
141
|
+
const duration = Math.min(1500, Math.max(200, distance * 8))
|
|
142
|
+
|
|
143
|
+
return withTiming(finalPosition, {
|
|
144
|
+
duration,
|
|
145
|
+
easing: Easing.out(Easing.cubic)
|
|
146
|
+
}, callback)
|
|
147
|
+
}
|
|
148
|
+
|
|
37
149
|
interface MovableViewProps {
|
|
38
150
|
children: ReactNode
|
|
39
151
|
style?: Record<string, any>
|
|
@@ -42,6 +154,8 @@ interface MovableViewProps {
|
|
|
42
154
|
y?: number
|
|
43
155
|
disabled?: boolean
|
|
44
156
|
animation?: boolean
|
|
157
|
+
damping?: number
|
|
158
|
+
friction?: number
|
|
45
159
|
id?: string
|
|
46
160
|
changeThrottleTime?:number
|
|
47
161
|
bindchange?: (event: unknown) => void
|
|
@@ -95,6 +209,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
|
|
|
95
209
|
inertia = false,
|
|
96
210
|
disabled = false,
|
|
97
211
|
animation = true,
|
|
212
|
+
damping = 20,
|
|
213
|
+
friction = 2,
|
|
98
214
|
'out-of-bounds': outOfBounds = false,
|
|
99
215
|
'enable-var': enableVar,
|
|
100
216
|
'external-var-context': externalVarContext,
|
|
@@ -206,18 +322,12 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
|
|
|
206
322
|
const { x: newX, y: newY } = checkBoundaryPosition({ positionX: Number(x), positionY: Number(y) })
|
|
207
323
|
if (direction === 'horizontal' || direction === 'all') {
|
|
208
324
|
offsetX.value = animation
|
|
209
|
-
?
|
|
210
|
-
duration: 1500,
|
|
211
|
-
dampingRatio: 0.8
|
|
212
|
-
})
|
|
325
|
+
? withWechatSpring(newX, damping)
|
|
213
326
|
: newX
|
|
214
327
|
}
|
|
215
328
|
if (direction === 'vertical' || direction === 'all') {
|
|
216
329
|
offsetY.value = animation
|
|
217
|
-
?
|
|
218
|
-
duration: 1500,
|
|
219
|
-
dampingRatio: 0.8
|
|
220
|
-
})
|
|
330
|
+
? withWechatSpring(newY, damping)
|
|
221
331
|
: newY
|
|
222
332
|
}
|
|
223
333
|
if (bindchange) {
|
|
@@ -404,7 +514,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
|
|
|
404
514
|
})
|
|
405
515
|
const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef)
|
|
406
516
|
|
|
407
|
-
// 节流版本的
|
|
517
|
+
// 节流版本的change事件触发
|
|
408
518
|
const handleTriggerChangeThrottled = useCallback(({ x, y, type }: { x: number; y: number; type?: string }) => {
|
|
409
519
|
'worklet'
|
|
410
520
|
const now = Date.now()
|
|
@@ -468,7 +578,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
|
|
|
468
578
|
const { x } = checkBoundaryPosition({ positionX: newX, positionY: offsetY.value })
|
|
469
579
|
offsetX.value = x
|
|
470
580
|
} else {
|
|
471
|
-
offsetX.value = newX
|
|
581
|
+
offsetX.value = applyBoundaryDecline(newX, draggableXRange.value)
|
|
472
582
|
}
|
|
473
583
|
}
|
|
474
584
|
if (direction === 'vertical' || direction === 'all') {
|
|
@@ -477,7 +587,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
|
|
|
477
587
|
const { y } = checkBoundaryPosition({ positionX: offsetX.value, positionY: newY })
|
|
478
588
|
offsetY.value = y
|
|
479
589
|
} else {
|
|
480
|
-
offsetY.value = newY
|
|
590
|
+
offsetY.value = applyBoundaryDecline(newY, draggableYRange.value)
|
|
481
591
|
}
|
|
482
592
|
}
|
|
483
593
|
if (bindchange) {
|
|
@@ -506,18 +616,12 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
|
|
|
506
616
|
if (x !== offsetX.value || y !== offsetY.value) {
|
|
507
617
|
if (x !== offsetX.value) {
|
|
508
618
|
offsetX.value = animation
|
|
509
|
-
?
|
|
510
|
-
duration: 1500,
|
|
511
|
-
dampingRatio: 0.8
|
|
512
|
-
})
|
|
619
|
+
? withWechatSpring(x, damping)
|
|
513
620
|
: x
|
|
514
621
|
}
|
|
515
622
|
if (y !== offsetY.value) {
|
|
516
623
|
offsetY.value = animation
|
|
517
|
-
?
|
|
518
|
-
duration: 1500,
|
|
519
|
-
dampingRatio: 0.8
|
|
520
|
-
})
|
|
624
|
+
? withWechatSpring(y, damping)
|
|
521
625
|
: y
|
|
522
626
|
}
|
|
523
627
|
if (bindchange) {
|
|
@@ -528,38 +632,42 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
|
|
|
528
632
|
}
|
|
529
633
|
}
|
|
530
634
|
} else if (inertia) {
|
|
531
|
-
// 惯性处理
|
|
635
|
+
// 惯性处理 - 使用微信小程序friction算法
|
|
532
636
|
if (direction === 'horizontal' || direction === 'all') {
|
|
533
637
|
xInertialMotion.value = true
|
|
534
|
-
offsetX.value =
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
638
|
+
offsetX.value = withWechatDecay(
|
|
639
|
+
e.velocityX / 10,
|
|
640
|
+
offsetX.value,
|
|
641
|
+
draggableXRange.value,
|
|
642
|
+
friction,
|
|
643
|
+
() => {
|
|
644
|
+
xInertialMotion.value = false
|
|
645
|
+
if (bindchange) {
|
|
646
|
+
runOnJS(runOnJSCallback)('handleTriggerChange', {
|
|
647
|
+
x: offsetX.value,
|
|
648
|
+
y: offsetY.value
|
|
649
|
+
})
|
|
650
|
+
}
|
|
545
651
|
}
|
|
546
|
-
|
|
652
|
+
)
|
|
547
653
|
}
|
|
548
654
|
if (direction === 'vertical' || direction === 'all') {
|
|
549
655
|
yInertialMotion.value = true
|
|
550
|
-
offsetY.value =
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
656
|
+
offsetY.value = withWechatDecay(
|
|
657
|
+
e.velocityY / 10,
|
|
658
|
+
offsetY.value,
|
|
659
|
+
draggableYRange.value,
|
|
660
|
+
friction,
|
|
661
|
+
() => {
|
|
662
|
+
yInertialMotion.value = false
|
|
663
|
+
if (bindchange) {
|
|
664
|
+
runOnJS(runOnJSCallback)('handleTriggerChange', {
|
|
665
|
+
x: offsetX.value,
|
|
666
|
+
y: offsetY.value
|
|
667
|
+
})
|
|
668
|
+
}
|
|
561
669
|
}
|
|
562
|
-
|
|
670
|
+
)
|
|
563
671
|
}
|
|
564
672
|
}
|
|
565
673
|
})
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback, forwardRef, ForwardedRef, useImperativeHandle, ReactNode, ReactElement } from 'react'
|
|
2
|
-
import { View, StyleSheet } from 'react-native'
|
|
1
|
+
import { useState, useCallback, forwardRef, ForwardedRef, useImperativeHandle, ReactNode, ReactElement, Fragment } from 'react'
|
|
3
2
|
|
|
4
3
|
export type State = {
|
|
5
4
|
portals: Array<{
|
|
@@ -48,13 +47,10 @@ const _PortalManager = forwardRef((props: PortalManagerProps, ref:ForwardedRef<u
|
|
|
48
47
|
|
|
49
48
|
return (
|
|
50
49
|
<>
|
|
51
|
-
{state.portals.map(({ key, children }
|
|
52
|
-
<
|
|
53
|
-
key={key}
|
|
54
|
-
collapsable={false} // Need collapsable=false here to clip the elevations
|
|
55
|
-
style={[StyleSheet.absoluteFill, { zIndex: 1000 + i, pointerEvents: 'box-none' }]}>
|
|
50
|
+
{state.portals.map(({ key, children }) => (
|
|
51
|
+
<Fragment key={key}>
|
|
56
52
|
{children}
|
|
57
|
-
</
|
|
53
|
+
</Fragment>
|
|
58
54
|
))}
|
|
59
55
|
</>
|
|
60
56
|
)
|