@mpxjs/webpack-plugin 2.10.7-beta.8 → 2.10.7-beta.9
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/platform/style/wx/index.js +7 -0
- package/lib/platform/template/wx/component-config/movable-view.js +1 -10
- package/lib/runtime/components/react/context.ts +12 -2
- package/lib/runtime/components/react/dist/context.js +1 -1
- package/lib/runtime/components/react/dist/mpx-movable-area.jsx +63 -9
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +306 -61
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +15 -10
- package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +3 -1
- package/lib/runtime/components/react/mpx-movable-area.tsx +98 -11
- package/lib/runtime/components/react/mpx-movable-view.tsx +356 -62
- package/lib/runtime/components/react/mpx-scroll-view.tsx +16 -9
- package/lib/runtime/components/react/mpx-sticky-header.tsx +3 -1
- package/lib/runtime/components/web/mpx-scroll-view.vue +4 -7
- package/lib/runtime/components/web/mpx-sticky-header.vue +39 -31
- package/lib/template-compiler/bind-this.js +2 -1
- package/lib/template-compiler/compiler.js +1 -1
- package/package.json +1 -1
|
@@ -285,6 +285,13 @@ module.exports = function getSpec ({ warn, error }) {
|
|
|
285
285
|
|
|
286
286
|
// line-height
|
|
287
287
|
const formatLineHeight = ({ prop, value, selector }) => {
|
|
288
|
+
// line-height 0 直接返回
|
|
289
|
+
if (+value === 0) {
|
|
290
|
+
return {
|
|
291
|
+
prop,
|
|
292
|
+
value
|
|
293
|
+
}
|
|
294
|
+
}
|
|
288
295
|
return verifyValues({ prop, value, selector }) && ({
|
|
289
296
|
prop,
|
|
290
297
|
value: /^\s*(-?(\d+(\.\d+)?|\.\d+))\s*$/.test(value) ? `${Math.round(value * 100)}%` : value
|
|
@@ -2,9 +2,6 @@ const TAG_NAME = 'movable-view'
|
|
|
2
2
|
|
|
3
3
|
module.exports = function ({ print }) {
|
|
4
4
|
const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' })
|
|
5
|
-
const androidEventLog = print({ platform: 'android', tag: TAG_NAME, isError: false, type: 'event' })
|
|
6
|
-
const harmonyEventLog = print({ platform: 'harmony', tag: TAG_NAME, isError: false, type: 'event' })
|
|
7
|
-
const iosEventLog = print({ platform: 'ios', tag: TAG_NAME, isError: false, type: 'event' })
|
|
8
5
|
const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
|
|
9
6
|
const androidPropLog = print({ platform: 'android', tag: TAG_NAME, isError: false })
|
|
10
7
|
const harmonyPropLog = print({ platform: 'harmony', tag: TAG_NAME, isError: false })
|
|
@@ -36,7 +33,7 @@ module.exports = function ({ print }) {
|
|
|
36
33
|
harmony: harmonyPropLog
|
|
37
34
|
},
|
|
38
35
|
{
|
|
39
|
-
test: /^(damping|friction
|
|
36
|
+
test: /^(damping|friction)$/,
|
|
40
37
|
ios: iosPropLog,
|
|
41
38
|
android: androidPropLog,
|
|
42
39
|
harmony: harmonyPropLog
|
|
@@ -46,12 +43,6 @@ module.exports = function ({ print }) {
|
|
|
46
43
|
{
|
|
47
44
|
test: /^(htouchmove|vtouchmove)$/,
|
|
48
45
|
ali: aliEventLog
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
test: /^(bindscale)$/,
|
|
52
|
-
ios: iosEventLog,
|
|
53
|
-
android: androidEventLog,
|
|
54
|
-
harmony: harmonyEventLog
|
|
55
46
|
}
|
|
56
47
|
]
|
|
57
48
|
}
|
|
@@ -51,14 +51,24 @@ export interface RouteContextValue {
|
|
|
51
51
|
pageId: number
|
|
52
52
|
navigation: Record<string, any>
|
|
53
53
|
}
|
|
54
|
+
export interface MovableAreaContextValue {
|
|
55
|
+
width: number
|
|
56
|
+
height: number
|
|
57
|
+
scaleArea: boolean
|
|
58
|
+
onAreaScale?: (scaleInfo: { scale: number }) => void
|
|
59
|
+
registerMovableView?: (id: string, callbacks: {
|
|
60
|
+
onScale: (scaleInfo: { scale: number }) => void
|
|
61
|
+
onScaleEnd?: () => void
|
|
62
|
+
}) => void
|
|
63
|
+
unregisterMovableView?: (id: string) => void
|
|
64
|
+
}
|
|
54
65
|
|
|
66
|
+
export const MovableAreaContext = createContext<MovableAreaContextValue>({ width: 0, height: 0, scaleArea: false })
|
|
55
67
|
export interface StickyContextValue {
|
|
56
68
|
registerStickyHeader: Function,
|
|
57
69
|
unregisterStickyHeader: Function
|
|
58
70
|
}
|
|
59
71
|
|
|
60
|
-
export const MovableAreaContext = createContext({ width: 0, height: 0 })
|
|
61
|
-
|
|
62
72
|
export const FormContext = createContext<FormContextValue | null>(null)
|
|
63
73
|
|
|
64
74
|
export const CheckboxGroupContext = createContext<GroupContextValue | null>(null)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
2
|
import { Animated } from 'react-native';
|
|
3
3
|
import { noop } from '@mpxjs/utils';
|
|
4
|
-
export const MovableAreaContext = createContext({ width: 0, height: 0 });
|
|
4
|
+
export const MovableAreaContext = createContext({ width: 0, height: 0, scaleArea: false });
|
|
5
5
|
export const FormContext = createContext(null);
|
|
6
6
|
export const CheckboxGroupContext = createContext(null);
|
|
7
7
|
export const RadioGroupContext = createContext(null);
|
|
@@ -1,33 +1,87 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ✔ scale-area
|
|
3
3
|
*/
|
|
4
4
|
import { View } from 'react-native';
|
|
5
|
-
import { forwardRef, useRef, useMemo, createElement } from 'react';
|
|
5
|
+
import { forwardRef, useRef, useMemo, useCallback, createElement } from 'react';
|
|
6
|
+
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
|
|
7
|
+
import { useSharedValue } from 'react-native-reanimated';
|
|
6
8
|
import useNodesRef from './useNodesRef';
|
|
7
9
|
import useInnerProps from './getInnerListeners';
|
|
8
10
|
import { MovableAreaContext } from './context';
|
|
9
11
|
import { useTransformStyle, wrapChildren, useLayout, extendObject } from './utils';
|
|
10
12
|
import Portal from './mpx-portal';
|
|
11
13
|
const _MovableArea = forwardRef((props, ref) => {
|
|
12
|
-
const { style = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
14
|
+
const { style = {}, 'scale-area': scaleArea = false, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
13
15
|
const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, hasPositionFixed, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
+
const movableAreaRef = useRef(null);
|
|
17
|
+
const movableViewsValue = useSharedValue({});
|
|
18
|
+
useNodesRef(props, ref, movableAreaRef, {
|
|
16
19
|
style: normalStyle
|
|
17
20
|
});
|
|
21
|
+
// 注册/注销 MovableView 的回调
|
|
22
|
+
const registerMovableView = useCallback((id, callbacks) => {
|
|
23
|
+
movableViewsValue.value = extendObject(movableViewsValue.value, { [id]: callbacks });
|
|
24
|
+
}, []);
|
|
25
|
+
const unregisterMovableView = useCallback((id) => {
|
|
26
|
+
delete movableViewsValue.value[id];
|
|
27
|
+
}, []);
|
|
28
|
+
// 处理区域缩放手势
|
|
29
|
+
const handleAreaScale = useCallback((scaleInfo) => {
|
|
30
|
+
'worklet';
|
|
31
|
+
if (scaleArea) {
|
|
32
|
+
// 将缩放信息广播给所有注册的 MovableView
|
|
33
|
+
Object.values(movableViewsValue.value).forEach((callbacks) => {
|
|
34
|
+
callbacks.onScale && callbacks.onScale(scaleInfo);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}, [scaleArea]);
|
|
38
|
+
// 处理区域缩放结束
|
|
39
|
+
const handleAreaScaleEnd = useCallback(() => {
|
|
40
|
+
'worklet';
|
|
41
|
+
if (scaleArea) {
|
|
42
|
+
// 通知所有注册的 MovableView 缩放结束
|
|
43
|
+
Object.values(movableViewsValue.value).forEach((callbacks) => {
|
|
44
|
+
callbacks.onScaleEnd && callbacks.onScaleEnd();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}, [scaleArea]);
|
|
18
48
|
const contextValue = useMemo(() => ({
|
|
19
49
|
height: normalStyle.height || 10,
|
|
20
|
-
width: normalStyle.width || 10
|
|
21
|
-
|
|
22
|
-
|
|
50
|
+
width: normalStyle.width || 10,
|
|
51
|
+
scaleArea,
|
|
52
|
+
registerMovableView,
|
|
53
|
+
unregisterMovableView
|
|
54
|
+
}), [normalStyle.width, normalStyle.height, scaleArea]);
|
|
55
|
+
const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: movableAreaRef });
|
|
56
|
+
// 创建缩放手势
|
|
57
|
+
const scaleGesture = useMemo(() => {
|
|
58
|
+
if (!scaleArea)
|
|
59
|
+
return null;
|
|
60
|
+
return Gesture.Pinch()
|
|
61
|
+
.onUpdate((e) => {
|
|
62
|
+
'worklet';
|
|
63
|
+
handleAreaScale(e);
|
|
64
|
+
})
|
|
65
|
+
.onEnd(() => {
|
|
66
|
+
'worklet';
|
|
67
|
+
handleAreaScaleEnd();
|
|
68
|
+
});
|
|
69
|
+
}, [scaleArea]);
|
|
23
70
|
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
24
71
|
style: extendObject({ height: contextValue.height, width: contextValue.width }, normalStyle, layoutStyle),
|
|
25
|
-
ref:
|
|
72
|
+
ref: movableAreaRef
|
|
26
73
|
}), [], { layoutRef });
|
|
27
74
|
let movableComponent = createElement(MovableAreaContext.Provider, { value: contextValue }, createElement(View, innerProps, wrapChildren(props, {
|
|
28
75
|
hasVarDec,
|
|
29
76
|
varContext: varContextRef.current
|
|
30
77
|
})));
|
|
78
|
+
// 如果启用了 scale-area,包装一个 GestureDetector
|
|
79
|
+
if (scaleArea && scaleGesture) {
|
|
80
|
+
movableComponent = createElement(MovableAreaContext.Provider, { value: contextValue }, createElement(GestureDetector, { gesture: scaleGesture }, createElement(View, innerProps, wrapChildren(props, {
|
|
81
|
+
hasVarDec,
|
|
82
|
+
varContext: varContextRef.current
|
|
83
|
+
}))));
|
|
84
|
+
}
|
|
31
85
|
if (hasPositionFixed) {
|
|
32
86
|
movableComponent = createElement(Portal, null, movableComponent);
|
|
33
87
|
}
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* ✘ damping
|
|
8
8
|
* ✘ friction
|
|
9
9
|
* ✔ disabled
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* ✔ scale
|
|
11
|
+
* ✔ scale-min
|
|
12
|
+
* ✔ scale-max
|
|
13
|
+
* ✔ scale-value
|
|
14
14
|
* ✔ animation
|
|
15
15
|
* ✔ bindchange
|
|
16
|
-
*
|
|
16
|
+
* ✔ bindscale
|
|
17
17
|
* ✔ htouchmove
|
|
18
18
|
* ✔ vtouchmove
|
|
19
19
|
*/
|
|
@@ -24,7 +24,7 @@ import useNodesRef from './useNodesRef';
|
|
|
24
24
|
import { MovableAreaContext } from './context';
|
|
25
25
|
import { useTransformStyle, splitProps, splitStyle, HIDDEN_STYLE, wrapChildren, flatGesture, extendObject, omit, useNavigation } from './utils';
|
|
26
26
|
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
|
|
27
|
-
import Animated, { useSharedValue, useAnimatedStyle, withDecay, runOnJS, runOnUI, withSpring } from 'react-native-reanimated';
|
|
27
|
+
import Animated, { useSharedValue, useAnimatedStyle, withDecay, runOnJS, runOnUI, withSpring, withTiming } from 'react-native-reanimated';
|
|
28
28
|
import { collectDataset, noop } from '@mpxjs/utils';
|
|
29
29
|
const styles = StyleSheet.create({
|
|
30
30
|
container: {
|
|
@@ -41,8 +41,8 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
41
41
|
const hasLayoutRef = useRef(false);
|
|
42
42
|
const propsRef = useRef({});
|
|
43
43
|
propsRef.current = (props || {});
|
|
44
|
-
const { x = 0, y = 0, inertia = false, disabled = false, animation = true, 'out-of-bounds': outOfBounds = false, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, direction = 'none', 'simultaneous-handlers': originSimultaneousHandlers = [], 'wait-for': waitFor = [], style = {}, changeThrottleTime = 60, bindtouchstart, catchtouchstart, bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove, bindtouchend, catchtouchend, bindchange } = props;
|
|
45
|
-
const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(Object.assign({},
|
|
44
|
+
const { x = 0, y = 0, inertia = false, disabled = false, animation = true, scale = false, 'scale-min': scaleMin = 0.1, 'scale-max': scaleMax = 10, 'scale-value': scaleValue = 1, 'out-of-bounds': outOfBounds = false, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, direction = 'none', 'disable-event-passthrough': disableEventPassthrough = false, 'simultaneous-handlers': originSimultaneousHandlers = [], 'wait-for': waitFor = [], style = {}, changeThrottleTime = 60, bindtouchstart, catchtouchstart, bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove, bindtouchend, catchtouchend, bindscale, bindchange, onLayout: propsOnLayout } = props;
|
|
45
|
+
const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(Object.assign({}, styles.container, style), { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
46
46
|
const navigation = useNavigation();
|
|
47
47
|
const prevSimultaneousHandlersRef = useRef(originSimultaneousHandlers || []);
|
|
48
48
|
const prevWaitForHandlersRef = useRef(waitFor || []);
|
|
@@ -50,6 +50,8 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
50
50
|
const { textStyle, innerStyle } = splitStyle(normalStyle);
|
|
51
51
|
const offsetX = useSharedValue(x);
|
|
52
52
|
const offsetY = useSharedValue(y);
|
|
53
|
+
const currentScale = useSharedValue(1);
|
|
54
|
+
const layoutValue = useSharedValue({});
|
|
53
55
|
const startPosition = useSharedValue({
|
|
54
56
|
x: 0,
|
|
55
57
|
y: 0
|
|
@@ -100,6 +102,38 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
100
102
|
layoutRef
|
|
101
103
|
}, propsRef.current));
|
|
102
104
|
}, []);
|
|
105
|
+
const handleTriggerScale = useCallback(({ x, y, scale }) => {
|
|
106
|
+
const { bindscale } = propsRef.current;
|
|
107
|
+
if (!bindscale)
|
|
108
|
+
return;
|
|
109
|
+
bindscale(getCustomEvent('scale', {}, {
|
|
110
|
+
detail: {
|
|
111
|
+
x,
|
|
112
|
+
y,
|
|
113
|
+
scale
|
|
114
|
+
},
|
|
115
|
+
layoutRef
|
|
116
|
+
}, propsRef.current));
|
|
117
|
+
}, []);
|
|
118
|
+
const checkBoundaryPosition = useCallback(({ positionX, positionY }) => {
|
|
119
|
+
'worklet';
|
|
120
|
+
let x = positionX;
|
|
121
|
+
let y = positionY;
|
|
122
|
+
// 计算边界限制
|
|
123
|
+
if (x > draggableXRange.value[1]) {
|
|
124
|
+
x = draggableXRange.value[1];
|
|
125
|
+
}
|
|
126
|
+
else if (x < draggableXRange.value[0]) {
|
|
127
|
+
x = draggableXRange.value[0];
|
|
128
|
+
}
|
|
129
|
+
if (y > draggableYRange.value[1]) {
|
|
130
|
+
y = draggableYRange.value[1];
|
|
131
|
+
}
|
|
132
|
+
else if (y < draggableYRange.value[0]) {
|
|
133
|
+
y = draggableYRange.value[0];
|
|
134
|
+
}
|
|
135
|
+
return { x, y };
|
|
136
|
+
}, []);
|
|
103
137
|
// 节流版本的 change 事件触发
|
|
104
138
|
const handleTriggerChangeThrottled = useCallback(({ x, y, type }) => {
|
|
105
139
|
'worklet';
|
|
@@ -139,12 +173,193 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
139
173
|
}
|
|
140
174
|
})();
|
|
141
175
|
}, [x, y]);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
176
|
+
// 提取通用的缩放边界计算函数
|
|
177
|
+
const calculateScaleBoundaryPosition = useCallback(({ currentOffsetX, currentOffsetY, newScale, width, height }) => {
|
|
178
|
+
'worklet';
|
|
179
|
+
const prevScale = currentScale.value;
|
|
180
|
+
// 计算元素当前中心点(在屏幕上的位置)
|
|
181
|
+
const currentCenterX = currentOffsetX + (width * prevScale) / 2;
|
|
182
|
+
const currentCenterY = currentOffsetY + (height * prevScale) / 2;
|
|
183
|
+
// 实现中心缩放:保持元素中心点不变
|
|
184
|
+
// 计算缩放后为了保持中心点不变需要的新offset位置
|
|
185
|
+
let newOffsetX = currentCenterX - (width * newScale) / 2;
|
|
186
|
+
let newOffsetY = currentCenterY - (height * newScale) / 2;
|
|
187
|
+
// 缩放过程中实时边界检测
|
|
188
|
+
// 计算新的边界范围
|
|
189
|
+
const top = (style.position === 'absolute' && style.top) || 0;
|
|
190
|
+
const left = (style.position === 'absolute' && style.left) || 0;
|
|
191
|
+
const scaledWidth = width * newScale;
|
|
192
|
+
const scaledHeight = height * newScale;
|
|
193
|
+
// 计算新缩放值下的边界限制
|
|
194
|
+
const maxOffsetY = MovableAreaLayout.height - scaledHeight - top;
|
|
195
|
+
const maxOffsetX = MovableAreaLayout.width - scaledWidth - left;
|
|
196
|
+
let xMin, xMax, yMin, yMax;
|
|
197
|
+
if (MovableAreaLayout.width < scaledWidth) {
|
|
198
|
+
xMin = maxOffsetX;
|
|
199
|
+
xMax = -left;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
xMin = -left;
|
|
203
|
+
xMax = maxOffsetX < 0 ? -left : maxOffsetX;
|
|
204
|
+
}
|
|
205
|
+
if (MovableAreaLayout.height < scaledHeight) {
|
|
206
|
+
yMin = maxOffsetY;
|
|
207
|
+
yMax = -top;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
yMin = -top;
|
|
211
|
+
yMax = maxOffsetY < 0 ? -top : maxOffsetY;
|
|
212
|
+
}
|
|
213
|
+
// 应用边界限制
|
|
214
|
+
if (newOffsetX > xMax) {
|
|
215
|
+
newOffsetX = xMax;
|
|
216
|
+
}
|
|
217
|
+
else if (newOffsetX < xMin) {
|
|
218
|
+
newOffsetX = xMin;
|
|
219
|
+
}
|
|
220
|
+
if (newOffsetY > yMax) {
|
|
221
|
+
newOffsetY = yMax;
|
|
222
|
+
}
|
|
223
|
+
else if (newOffsetY < yMin) {
|
|
224
|
+
newOffsetY = yMin;
|
|
225
|
+
}
|
|
226
|
+
return { x: newOffsetX, y: newOffsetY };
|
|
227
|
+
}, [MovableAreaLayout.height, MovableAreaLayout.width, style.position, style.top, style.left]);
|
|
228
|
+
// 提取通用的缩放处理函数
|
|
229
|
+
const handleScaleUpdate = useCallback((scaleInfo) => {
|
|
230
|
+
'worklet';
|
|
231
|
+
if (disabled)
|
|
232
|
+
return;
|
|
233
|
+
// 判断缩放方向并计算新的缩放值
|
|
234
|
+
const isZoomingIn = scaleInfo.scale > 1;
|
|
235
|
+
const isZoomingOut = scaleInfo.scale < 1;
|
|
236
|
+
let newScale;
|
|
237
|
+
if (isZoomingIn) {
|
|
238
|
+
// 放大:增加缩放值
|
|
239
|
+
newScale = currentScale.value + (scaleInfo.scale - 1) * 0.5;
|
|
240
|
+
}
|
|
241
|
+
else if (isZoomingOut) {
|
|
242
|
+
// 缩小:减少缩放值
|
|
243
|
+
newScale = currentScale.value - (1 - scaleInfo.scale) * 0.5;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// 没有缩放变化
|
|
247
|
+
newScale = currentScale.value;
|
|
248
|
+
}
|
|
249
|
+
// 限制缩放值在 scaleMin 和 scaleMax 之间
|
|
250
|
+
newScale = Math.max(scaleMin, Math.min(scaleMax, newScale));
|
|
251
|
+
// 只有当缩放值真正改变时才调整位置
|
|
252
|
+
if (Math.abs(newScale - currentScale.value) > 0.01) {
|
|
253
|
+
// 获取元素尺寸
|
|
254
|
+
const { width = 0, height = 0 } = layoutValue.value;
|
|
255
|
+
if (width > 0 && height > 0) {
|
|
256
|
+
// 使用通用的边界计算函数
|
|
257
|
+
const { x: newOffsetX, y: newOffsetY } = calculateScaleBoundaryPosition({
|
|
258
|
+
currentOffsetX: offsetX.value,
|
|
259
|
+
currentOffsetY: offsetY.value,
|
|
260
|
+
newScale,
|
|
261
|
+
width,
|
|
262
|
+
height
|
|
263
|
+
});
|
|
264
|
+
offsetX.value = newOffsetX;
|
|
265
|
+
offsetY.value = newOffsetY;
|
|
266
|
+
// 更新缩放值
|
|
267
|
+
currentScale.value = newScale;
|
|
268
|
+
}
|
|
146
269
|
}
|
|
270
|
+
else {
|
|
271
|
+
currentScale.value = newScale;
|
|
272
|
+
}
|
|
273
|
+
if (bindscale) {
|
|
274
|
+
runOnJS(handleTriggerScale)({
|
|
275
|
+
x: offsetX.value,
|
|
276
|
+
y: offsetY.value,
|
|
277
|
+
scale: newScale
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}, [disabled, scaleMin, scaleMax, bindscale, handleTriggerScale, calculateScaleBoundaryPosition, style.position, style.top, style.left, MovableAreaLayout.height, MovableAreaLayout.width]);
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
runOnUI(() => {
|
|
283
|
+
if (currentScale.value !== scaleValue) {
|
|
284
|
+
// 限制缩放值在 scaleMin 和 scaleMax 之间
|
|
285
|
+
const clampedScale = Math.max(scaleMin, Math.min(scaleMax, scaleValue));
|
|
286
|
+
// 实现中心缩放的位置补偿
|
|
287
|
+
const { width = 0, height = 0 } = layoutValue.value;
|
|
288
|
+
if (width > 0 && height > 0) {
|
|
289
|
+
// 使用通用的边界计算函数
|
|
290
|
+
const { x: newOffsetX, y: newOffsetY } = calculateScaleBoundaryPosition({
|
|
291
|
+
currentOffsetX: offsetX.value,
|
|
292
|
+
currentOffsetY: offsetY.value,
|
|
293
|
+
newScale: clampedScale,
|
|
294
|
+
width,
|
|
295
|
+
height
|
|
296
|
+
});
|
|
297
|
+
// 同时更新缩放值和位置
|
|
298
|
+
if (animation) {
|
|
299
|
+
currentScale.value = withTiming(clampedScale, {
|
|
300
|
+
duration: 1000
|
|
301
|
+
}, () => {
|
|
302
|
+
handleRestBoundaryAndCheck();
|
|
303
|
+
});
|
|
304
|
+
offsetX.value = withTiming(newOffsetX, { duration: 1000 });
|
|
305
|
+
offsetY.value = withTiming(newOffsetY, { duration: 1000 });
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
currentScale.value = clampedScale;
|
|
309
|
+
offsetX.value = newOffsetX;
|
|
310
|
+
offsetY.value = newOffsetY;
|
|
311
|
+
handleRestBoundaryAndCheck();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
// 如果还没有尺寸信息,只更新缩放值
|
|
316
|
+
if (animation) {
|
|
317
|
+
currentScale.value = withTiming(clampedScale, {
|
|
318
|
+
duration: 1000
|
|
319
|
+
}, () => {
|
|
320
|
+
handleRestBoundaryAndCheck();
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
currentScale.value = clampedScale;
|
|
325
|
+
handleRestBoundaryAndCheck();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (bindscale) {
|
|
329
|
+
runOnJS(handleTriggerScale)({
|
|
330
|
+
x: offsetX.value,
|
|
331
|
+
y: offsetY.value,
|
|
332
|
+
scale: clampedScale
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
})();
|
|
337
|
+
}, [scaleValue, scaleMin, scaleMax, animation]);
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
runOnUI(handleRestBoundaryAndCheck)();
|
|
147
340
|
}, [MovableAreaLayout.height, MovableAreaLayout.width]);
|
|
341
|
+
// 生成唯一 ID
|
|
342
|
+
const viewId = useMemo(() => `movable-view-${Date.now()}-${Math.random()}`, []);
|
|
343
|
+
// 注册到 MovableArea(如果启用了 scale-area)
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
if (MovableAreaLayout.scaleArea && MovableAreaLayout.registerMovableView && MovableAreaLayout.unregisterMovableView) {
|
|
346
|
+
const handleAreaScale = (scaleInfo) => {
|
|
347
|
+
'worklet';
|
|
348
|
+
handleScaleUpdate({ scale: scaleInfo.scale });
|
|
349
|
+
};
|
|
350
|
+
const handleAreaScaleEnd = () => {
|
|
351
|
+
'worklet';
|
|
352
|
+
handleRestBoundaryAndCheck();
|
|
353
|
+
};
|
|
354
|
+
MovableAreaLayout.registerMovableView?.(viewId, {
|
|
355
|
+
onScale: scale ? handleAreaScale : noop,
|
|
356
|
+
onScaleEnd: scale ? handleAreaScaleEnd : noop
|
|
357
|
+
});
|
|
358
|
+
return () => {
|
|
359
|
+
MovableAreaLayout.unregisterMovableView?.(viewId);
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}, [MovableAreaLayout.scaleArea, viewId, scale, handleScaleUpdate]);
|
|
148
363
|
const getTouchSource = useCallback((offsetX, offsetY) => {
|
|
149
364
|
const hasOverBoundary = offsetX < draggableXRange.value[0] || offsetX > draggableXRange.value[1] ||
|
|
150
365
|
offsetY < draggableYRange.value[0] || offsetY > draggableYRange.value[1];
|
|
@@ -169,61 +384,45 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
169
384
|
return source;
|
|
170
385
|
}, []);
|
|
171
386
|
const setBoundary = useCallback(({ width, height }) => {
|
|
387
|
+
'worklet';
|
|
172
388
|
const top = (style.position === 'absolute' && style.top) || 0;
|
|
173
389
|
const left = (style.position === 'absolute' && style.left) || 0;
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
390
|
+
// 使用左上角缩放,计算offset位置的边界范围
|
|
391
|
+
const currentScaleVal = currentScale.value;
|
|
392
|
+
const scaledWidth = (width || 0) * currentScaleVal;
|
|
393
|
+
const scaledHeight = (height || 0) * currentScaleVal;
|
|
394
|
+
// offset位置的边界:左上角可以移动的范围
|
|
395
|
+
const maxOffsetY = MovableAreaLayout.height - scaledHeight - top;
|
|
396
|
+
const maxOffsetX = MovableAreaLayout.width - scaledWidth - left;
|
|
178
397
|
let xRange;
|
|
179
398
|
let yRange;
|
|
180
399
|
if (MovableAreaLayout.width < scaledWidth) {
|
|
181
|
-
xRange = [
|
|
400
|
+
xRange = [maxOffsetX, -left];
|
|
182
401
|
}
|
|
183
402
|
else {
|
|
184
|
-
xRange = [
|
|
403
|
+
xRange = [-left, maxOffsetX < 0 ? -left : maxOffsetX];
|
|
185
404
|
}
|
|
186
405
|
if (MovableAreaLayout.height < scaledHeight) {
|
|
187
|
-
yRange = [
|
|
406
|
+
yRange = [maxOffsetY, -top];
|
|
188
407
|
}
|
|
189
408
|
else {
|
|
190
|
-
yRange = [
|
|
409
|
+
yRange = [-top, maxOffsetY < 0 ? -top : maxOffsetY];
|
|
191
410
|
}
|
|
192
411
|
draggableXRange.value = xRange;
|
|
193
412
|
draggableYRange.value = yRange;
|
|
194
413
|
}, [MovableAreaLayout.height, MovableAreaLayout.width, style.position, style.top, style.left]);
|
|
195
|
-
const
|
|
414
|
+
const resetBoundaryAndCheck = ({ width, height }) => {
|
|
196
415
|
'worklet';
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
else if (x < draggableXRange.value[0]) {
|
|
204
|
-
x = draggableXRange.value[0];
|
|
205
|
-
}
|
|
206
|
-
if (y > draggableYRange.value[1]) {
|
|
207
|
-
y = draggableYRange.value[1];
|
|
416
|
+
setBoundary({ width, height });
|
|
417
|
+
const positionX = offsetX.value;
|
|
418
|
+
const positionY = offsetY.value;
|
|
419
|
+
const { x: newX, y: newY } = checkBoundaryPosition({ positionX, positionY });
|
|
420
|
+
if (positionX !== newX) {
|
|
421
|
+
offsetX.value = newX;
|
|
208
422
|
}
|
|
209
|
-
|
|
210
|
-
|
|
423
|
+
if (positionY !== newY) {
|
|
424
|
+
offsetY.value = newY;
|
|
211
425
|
}
|
|
212
|
-
return { x, y };
|
|
213
|
-
}, []);
|
|
214
|
-
const resetBoundaryAndCheck = ({ width, height }) => {
|
|
215
|
-
setBoundary({ width, height });
|
|
216
|
-
runOnUI(() => {
|
|
217
|
-
const positionX = offsetX.value;
|
|
218
|
-
const positionY = offsetY.value;
|
|
219
|
-
const { x: newX, y: newY } = checkBoundaryPosition({ positionX, positionY });
|
|
220
|
-
if (positionX !== newX) {
|
|
221
|
-
offsetX.value = newX;
|
|
222
|
-
}
|
|
223
|
-
if (positionY !== newY) {
|
|
224
|
-
offsetY.value = newY;
|
|
225
|
-
}
|
|
226
|
-
})();
|
|
227
426
|
};
|
|
228
427
|
const onLayout = (e) => {
|
|
229
428
|
hasLayoutRef.current = true;
|
|
@@ -235,13 +434,18 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
235
434
|
nodeRef.current?.measure((x, y, width, height) => {
|
|
236
435
|
const { y: navigationY = 0 } = navigation?.layout || {};
|
|
237
436
|
layoutRef.current = { x, y: y - navigationY, width, height, offsetLeft: 0, offsetTop: 0 };
|
|
238
|
-
|
|
437
|
+
// 同时更新 layoutValue,供缩放逻辑使用
|
|
438
|
+
runOnUI(() => {
|
|
439
|
+
layoutValue.value = { width, height };
|
|
440
|
+
resetBoundaryAndCheck({ width: width, height: height });
|
|
441
|
+
})();
|
|
239
442
|
});
|
|
240
|
-
|
|
443
|
+
propsOnLayout && propsOnLayout(e);
|
|
241
444
|
};
|
|
242
445
|
const extendEvent = useCallback((e, type) => {
|
|
243
446
|
const { y: navigationY = 0 } = navigation?.layout || {};
|
|
244
447
|
const touchArr = [e.changedTouches, e.allTouches];
|
|
448
|
+
const currentProps = propsRef.current;
|
|
245
449
|
touchArr.forEach(touches => {
|
|
246
450
|
touches && touches.forEach((item) => {
|
|
247
451
|
item.pageX = item.absoluteX;
|
|
@@ -253,8 +457,8 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
253
457
|
Object.assign(e, {
|
|
254
458
|
touches: type === 'end' ? [] : e.allTouches,
|
|
255
459
|
currentTarget: {
|
|
256
|
-
id:
|
|
257
|
-
dataset: collectDataset(
|
|
460
|
+
id: currentProps.id || '',
|
|
461
|
+
dataset: collectDataset(currentProps),
|
|
258
462
|
offsetLeft: 0,
|
|
259
463
|
offsetTop: 0
|
|
260
464
|
},
|
|
@@ -295,6 +499,13 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
295
499
|
bindtouchend && bindtouchend(e);
|
|
296
500
|
catchtouchend && catchtouchend(e);
|
|
297
501
|
};
|
|
502
|
+
const handleRestBoundaryAndCheck = () => {
|
|
503
|
+
'worklet';
|
|
504
|
+
const { width, height } = layoutValue.value;
|
|
505
|
+
if (width && height) {
|
|
506
|
+
resetBoundaryAndCheck({ width, height });
|
|
507
|
+
}
|
|
508
|
+
};
|
|
298
509
|
const gesture = useMemo(() => {
|
|
299
510
|
const handleTriggerMove = (e) => {
|
|
300
511
|
'worklet';
|
|
@@ -310,6 +521,8 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
310
521
|
}
|
|
311
522
|
};
|
|
312
523
|
const gesturePan = Gesture.Pan()
|
|
524
|
+
.minPointers(1)
|
|
525
|
+
.maxPointers(1)
|
|
313
526
|
.onTouchesDown((e) => {
|
|
314
527
|
'worklet';
|
|
315
528
|
const changedTouches = e.changedTouches[0] || { x: 0, y: 0 };
|
|
@@ -449,11 +662,13 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
449
662
|
}
|
|
450
663
|
})
|
|
451
664
|
.withRef(movableGestureRef);
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
665
|
+
if (!disableEventPassthrough) {
|
|
666
|
+
if (direction === 'horizontal') {
|
|
667
|
+
gesturePan.activeOffsetX([-5, 5]).failOffsetY([-5, 5]);
|
|
668
|
+
}
|
|
669
|
+
else if (direction === 'vertical') {
|
|
670
|
+
gesturePan.activeOffsetY([-5, 5]).failOffsetX([-5, 5]);
|
|
671
|
+
}
|
|
457
672
|
}
|
|
458
673
|
if (simultaneousHandlers && simultaneousHandlers.length) {
|
|
459
674
|
gesturePan.simultaneousWithExternalGesture(...simultaneousHandlers);
|
|
@@ -461,13 +676,43 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
461
676
|
if (waitForHandlers && waitForHandlers.length) {
|
|
462
677
|
gesturePan.requireExternalGestureToFail(...waitForHandlers);
|
|
463
678
|
}
|
|
679
|
+
// 添加缩放手势支持
|
|
680
|
+
if (scale && !MovableAreaLayout.scaleArea) {
|
|
681
|
+
const gesturePinch = Gesture.Pinch()
|
|
682
|
+
.onUpdate((e) => {
|
|
683
|
+
'worklet';
|
|
684
|
+
handleScaleUpdate({ scale: e.scale });
|
|
685
|
+
})
|
|
686
|
+
.onEnd((e) => {
|
|
687
|
+
'worklet';
|
|
688
|
+
if (disabled)
|
|
689
|
+
return;
|
|
690
|
+
// 确保最终缩放值在有效范围内
|
|
691
|
+
const finalScale = Math.max(scaleMin, Math.min(scaleMax, currentScale.value));
|
|
692
|
+
if (finalScale !== currentScale.value) {
|
|
693
|
+
currentScale.value = finalScale;
|
|
694
|
+
if (bindscale) {
|
|
695
|
+
runOnJS(handleTriggerScale)({
|
|
696
|
+
x: offsetX.value,
|
|
697
|
+
y: offsetY.value,
|
|
698
|
+
scale: finalScale
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// 缩放结束后重新检查边界
|
|
703
|
+
handleRestBoundaryAndCheck();
|
|
704
|
+
});
|
|
705
|
+
// 根据手指数量自动区分手势:一指移动,两指缩放
|
|
706
|
+
return Gesture.Exclusive(gesturePan, gesturePinch);
|
|
707
|
+
}
|
|
464
708
|
return gesturePan;
|
|
465
|
-
}, [disabled, direction, inertia, outOfBounds, gestureSwitch.current]);
|
|
709
|
+
}, [disabled, direction, inertia, outOfBounds, scale, scaleMin, scaleMax, animation, gestureSwitch.current, handleScaleUpdate, MovableAreaLayout.scaleArea]);
|
|
466
710
|
const animatedStyles = useAnimatedStyle(() => {
|
|
467
711
|
return {
|
|
468
712
|
transform: [
|
|
469
713
|
{ translateX: offsetX.value },
|
|
470
|
-
{ translateY: offsetY.value }
|
|
714
|
+
{ translateY: offsetY.value },
|
|
715
|
+
{ scale: currentScale.value }
|
|
471
716
|
]
|
|
472
717
|
};
|
|
473
718
|
});
|
|
@@ -504,7 +749,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
|
|
|
504
749
|
const innerProps = useInnerProps(extendObject({}, filterProps, {
|
|
505
750
|
ref: nodeRef,
|
|
506
751
|
onLayout: onLayout,
|
|
507
|
-
style: [innerStyle, animatedStyles, layoutStyle]
|
|
752
|
+
style: [{ transformOrigin: 'top left' }, innerStyle, animatedStyles, layoutStyle]
|
|
508
753
|
}, rewriteCatchEvent()));
|
|
509
754
|
return createElement(GestureDetector, { gesture: gesture }, createElement(Animated.View, innerProps, wrapChildren(props, {
|
|
510
755
|
hasVarDec,
|