@mpxjs/webpack-plugin 2.10.6 → 2.10.7-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/lib/dependencies/RecordPageConfigsMapDependency.js +1 -1
  2. package/lib/index.js +71 -51
  3. package/lib/parser.js +1 -1
  4. package/lib/platform/json/wx/index.js +0 -1
  5. package/lib/platform/style/wx/index.js +7 -0
  6. package/lib/platform/template/wx/component-config/button.js +1 -1
  7. package/lib/platform/template/wx/component-config/index.js +5 -1
  8. package/lib/platform/template/wx/component-config/input.js +1 -1
  9. package/lib/platform/template/wx/component-config/movable-view.js +1 -10
  10. package/lib/platform/template/wx/component-config/sticky-header.js +23 -0
  11. package/lib/platform/template/wx/component-config/sticky-section.js +23 -0
  12. package/lib/react/processJSON.js +2 -1
  13. package/lib/runtime/components/react/AsyncContainer.tsx +189 -0
  14. package/lib/runtime/components/react/context.ts +23 -4
  15. package/lib/runtime/components/react/dist/AsyncContainer.jsx +141 -0
  16. package/lib/runtime/components/react/dist/context.js +5 -2
  17. package/lib/runtime/components/react/dist/mpx-button.jsx +2 -2
  18. package/lib/runtime/components/react/dist/mpx-input.jsx +1 -1
  19. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +64 -10
  20. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +358 -98
  21. package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +3 -0
  22. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +31 -15
  23. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +117 -0
  24. package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +45 -0
  25. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +2 -2
  26. package/lib/runtime/components/react/dist/mpx-swiper.jsx +53 -27
  27. package/lib/runtime/components/react/dist/mpx-view.jsx +21 -7
  28. package/lib/runtime/components/react/dist/mpx-web-view.jsx +13 -13
  29. package/lib/runtime/components/react/dist/utils.jsx +94 -1
  30. package/lib/runtime/components/react/mpx-button.tsx +3 -2
  31. package/lib/runtime/components/react/mpx-input.tsx +1 -1
  32. package/lib/runtime/components/react/mpx-movable-area.tsx +99 -12
  33. package/lib/runtime/components/react/mpx-movable-view.tsx +413 -100
  34. package/lib/runtime/components/react/mpx-rich-text/index.tsx +3 -0
  35. package/lib/runtime/components/react/mpx-scroll-view.tsx +84 -59
  36. package/lib/runtime/components/react/mpx-sticky-header.tsx +181 -0
  37. package/lib/runtime/components/react/mpx-sticky-section.tsx +96 -0
  38. package/lib/runtime/components/react/mpx-swiper-item.tsx +2 -2
  39. package/lib/runtime/components/react/mpx-swiper.tsx +53 -25
  40. package/lib/runtime/components/react/mpx-view.tsx +20 -7
  41. package/lib/runtime/components/react/mpx-web-view.tsx +12 -12
  42. package/lib/runtime/components/react/utils.tsx +93 -1
  43. package/lib/runtime/components/web/mpx-scroll-view.vue +18 -4
  44. package/lib/runtime/components/web/mpx-sticky-header.vue +99 -0
  45. package/lib/runtime/components/web/mpx-sticky-section.vue +15 -0
  46. package/lib/runtime/optionProcessor.js +0 -2
  47. package/lib/script-setup-compiler/index.js +27 -5
  48. package/lib/template-compiler/bind-this.js +2 -1
  49. package/lib/template-compiler/compiler.js +4 -3
  50. package/package.json +4 -4
  51. package/LICENSE +0 -433
@@ -7,13 +7,13 @@
7
7
  * ✘ damping
8
8
  * ✘ friction
9
9
  * ✔ disabled
10
- * scale
11
- * scale-min
12
- * scale-max
13
- * scale-value
10
+ * scale
11
+ * scale-min
12
+ * scale-max
13
+ * scale-value
14
14
  * ✔ animation
15
15
  * ✔ bindchange
16
- * bindscale
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 = {}, bindtouchstart, catchtouchstart, bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove, bindtouchend, catchtouchend } = props;
45
- const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(Object.assign({}, style, styles.container), { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
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
@@ -62,6 +64,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
62
64
  const isFirstTouch = useSharedValue(true);
63
65
  const touchEvent = useSharedValue('');
64
66
  const initialViewPosition = useSharedValue({ x: x || 0, y: y || 0 });
67
+ const lastChangeTime = useSharedValue(0);
65
68
  const MovableAreaLayout = useContext(MovableAreaContext);
66
69
  const simultaneousHandlers = flatGesture(originSimultaneousHandlers);
67
70
  const waitForHandlers = flatGesture(waitFor);
@@ -99,6 +102,47 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
99
102
  layoutRef
100
103
  }, propsRef.current));
101
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
+ }, []);
137
+ // 节流版本的 change 事件触发
138
+ const handleTriggerChangeThrottled = useCallback(({ x, y, type }) => {
139
+ 'worklet';
140
+ const now = Date.now();
141
+ if (now - lastChangeTime.value >= changeThrottleTime) {
142
+ lastChangeTime.value = now;
143
+ runOnJS(handleTriggerChange)({ x, y, type });
144
+ }
145
+ }, [changeThrottleTime]);
102
146
  useEffect(() => {
103
147
  runOnUI(() => {
104
148
  if (offsetX.value !== x || offsetY.value !== y) {
@@ -119,7 +163,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
119
163
  })
120
164
  : newY;
121
165
  }
122
- if (propsRef.current.bindchange) {
166
+ if (bindchange) {
123
167
  runOnJS(handleTriggerChange)({
124
168
  x: newX,
125
169
  y: newY,
@@ -129,12 +173,193 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
129
173
  }
130
174
  })();
131
175
  }, [x, y]);
132
- useEffect(() => {
133
- const { width, height } = layoutRef.current;
134
- if (width && height) {
135
- resetBoundaryAndCheck({ width, height });
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;
136
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
+ }
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)();
137
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]);
138
363
  const getTouchSource = useCallback((offsetX, offsetY) => {
139
364
  const hasOverBoundary = offsetX < draggableXRange.value[0] || offsetX > draggableXRange.value[1] ||
140
365
  offsetY < draggableYRange.value[0] || offsetY > draggableYRange.value[1];
@@ -159,61 +384,45 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
159
384
  return source;
160
385
  }, []);
161
386
  const setBoundary = useCallback(({ width, height }) => {
387
+ 'worklet';
162
388
  const top = (style.position === 'absolute' && style.top) || 0;
163
389
  const left = (style.position === 'absolute' && style.left) || 0;
164
- const scaledWidth = width || 0;
165
- const scaledHeight = height || 0;
166
- const maxY = MovableAreaLayout.height - scaledHeight - top;
167
- const maxX = MovableAreaLayout.width - scaledWidth - left;
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;
168
397
  let xRange;
169
398
  let yRange;
170
399
  if (MovableAreaLayout.width < scaledWidth) {
171
- xRange = [maxX, 0];
400
+ xRange = [maxOffsetX, -left];
172
401
  }
173
402
  else {
174
- xRange = [left === 0 ? 0 : -left, maxX < 0 ? 0 : maxX];
403
+ xRange = [-left, maxOffsetX < 0 ? -left : maxOffsetX];
175
404
  }
176
405
  if (MovableAreaLayout.height < scaledHeight) {
177
- yRange = [maxY, 0];
406
+ yRange = [maxOffsetY, -top];
178
407
  }
179
408
  else {
180
- yRange = [top === 0 ? 0 : -top, maxY < 0 ? 0 : maxY];
409
+ yRange = [-top, maxOffsetY < 0 ? -top : maxOffsetY];
181
410
  }
182
411
  draggableXRange.value = xRange;
183
412
  draggableYRange.value = yRange;
184
413
  }, [MovableAreaLayout.height, MovableAreaLayout.width, style.position, style.top, style.left]);
185
- const checkBoundaryPosition = useCallback(({ positionX, positionY }) => {
414
+ const resetBoundaryAndCheck = ({ width, height }) => {
186
415
  'worklet';
187
- let x = positionX;
188
- let y = positionY;
189
- // 计算边界限制
190
- if (x > draggableXRange.value[1]) {
191
- x = draggableXRange.value[1];
192
- }
193
- else if (x < draggableXRange.value[0]) {
194
- x = draggableXRange.value[0];
195
- }
196
- if (y > draggableYRange.value[1]) {
197
- 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;
198
422
  }
199
- else if (y < draggableYRange.value[0]) {
200
- y = draggableYRange.value[0];
423
+ if (positionY !== newY) {
424
+ offsetY.value = newY;
201
425
  }
202
- return { x, y };
203
- }, []);
204
- const resetBoundaryAndCheck = ({ width, height }) => {
205
- setBoundary({ width, height });
206
- runOnUI(() => {
207
- const positionX = offsetX.value;
208
- const positionY = offsetY.value;
209
- const { x: newX, y: newY } = checkBoundaryPosition({ positionX, positionY });
210
- if (positionX !== newX) {
211
- offsetX.value = newX;
212
- }
213
- if (positionY !== newY) {
214
- offsetY.value = newY;
215
- }
216
- })();
217
426
  };
218
427
  const onLayout = (e) => {
219
428
  hasLayoutRef.current = true;
@@ -225,13 +434,18 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
225
434
  nodeRef.current?.measure((x, y, width, height) => {
226
435
  const { y: navigationY = 0 } = navigation?.layout || {};
227
436
  layoutRef.current = { x, y: y - navigationY, width, height, offsetLeft: 0, offsetTop: 0 };
228
- resetBoundaryAndCheck({ width, height });
437
+ // 同时更新 layoutValue,供缩放逻辑使用
438
+ runOnUI(() => {
439
+ layoutValue.value = { width, height };
440
+ resetBoundaryAndCheck({ width: width, height: height });
441
+ })();
229
442
  });
230
- props.onLayout && props.onLayout(e);
443
+ propsOnLayout && propsOnLayout(e);
231
444
  };
232
445
  const extendEvent = useCallback((e, type) => {
233
446
  const { y: navigationY = 0 } = navigation?.layout || {};
234
447
  const touchArr = [e.changedTouches, e.allTouches];
448
+ const currentProps = propsRef.current;
235
449
  touchArr.forEach(touches => {
236
450
  touches && touches.forEach((item) => {
237
451
  item.pageX = item.absoluteX;
@@ -243,8 +457,8 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
243
457
  Object.assign(e, {
244
458
  touches: type === 'end' ? [] : e.allTouches,
245
459
  currentTarget: {
246
- id: props.id || '',
247
- dataset: collectDataset(props),
460
+ id: currentProps.id || '',
461
+ dataset: collectDataset(currentProps),
248
462
  offsetLeft: 0,
249
463
  offsetTop: 0
250
464
  },
@@ -252,11 +466,13 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
252
466
  });
253
467
  }, []);
254
468
  const triggerStartOnJS = ({ e }) => {
469
+ const { bindtouchstart, catchtouchstart } = propsRef.current;
255
470
  extendEvent(e, 'start');
256
471
  bindtouchstart && bindtouchstart(e);
257
472
  catchtouchstart && catchtouchstart(e);
258
473
  };
259
474
  const triggerMoveOnJS = ({ e, hasTouchmove, hasCatchTouchmove, touchEvent }) => {
475
+ const { bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove } = propsRef.current;
260
476
  extendEvent(e, 'move');
261
477
  if (hasTouchmove) {
262
478
  if (touchEvent === 'htouchmove') {
@@ -278,10 +494,18 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
278
494
  }
279
495
  };
280
496
  const triggerEndOnJS = ({ e }) => {
497
+ const { bindtouchend, catchtouchend } = propsRef.current;
281
498
  extendEvent(e, 'end');
282
499
  bindtouchend && bindtouchend(e);
283
500
  catchtouchend && catchtouchend(e);
284
501
  };
502
+ const handleRestBoundaryAndCheck = () => {
503
+ 'worklet';
504
+ const { width, height } = layoutValue.value;
505
+ if (width && height) {
506
+ resetBoundaryAndCheck({ width, height });
507
+ }
508
+ };
285
509
  const gesture = useMemo(() => {
286
510
  const handleTriggerMove = (e) => {
287
511
  'worklet';
@@ -297,6 +521,8 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
297
521
  }
298
522
  };
299
523
  const gesturePan = Gesture.Pan()
524
+ .minPointers(1)
525
+ .maxPointers(1)
300
526
  .onTouchesDown((e) => {
301
527
  'worklet';
302
528
  const changedTouches = e.changedTouches[0] || { x: 0, y: 0 };
@@ -350,8 +576,9 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
350
576
  offsetY.value = newY;
351
577
  }
352
578
  }
353
- if (propsRef.current.bindchange) {
354
- runOnJS(handleTriggerChange)({
579
+ if (bindchange) {
580
+ // 使用节流版本减少 runOnJS 调用
581
+ handleTriggerChangeThrottled({
355
582
  x: offsetX.value,
356
583
  y: offsetY.value
357
584
  });
@@ -390,55 +617,58 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
390
617
  })
391
618
  : y;
392
619
  }
393
- if (propsRef.current.bindchange) {
620
+ if (bindchange) {
394
621
  runOnJS(handleTriggerChange)({
395
622
  x,
396
623
  y
397
624
  });
398
625
  }
399
626
  }
400
- return;
401
- }
402
- // 惯性处理
403
- if (direction === 'horizontal' || direction === 'all') {
404
- xInertialMotion.value = true;
405
- offsetX.value = withDecay({
406
- velocity: e.velocityX / 10,
407
- rubberBandEffect: outOfBounds,
408
- clamp: draggableXRange.value
409
- }, () => {
410
- xInertialMotion.value = false;
411
- if (propsRef.current.bindchange) {
412
- runOnJS(handleTriggerChange)({
413
- x: offsetX.value,
414
- y: offsetY.value
415
- });
416
- }
417
- });
418
627
  }
419
- if (direction === 'vertical' || direction === 'all') {
420
- yInertialMotion.value = true;
421
- offsetY.value = withDecay({
422
- velocity: e.velocityY / 10,
423
- rubberBandEffect: outOfBounds,
424
- clamp: draggableYRange.value
425
- }, () => {
426
- yInertialMotion.value = false;
427
- if (propsRef.current.bindchange) {
428
- runOnJS(handleTriggerChange)({
429
- x: offsetX.value,
430
- y: offsetY.value
431
- });
432
- }
433
- });
628
+ else if (inertia) {
629
+ // 惯性处理
630
+ if (direction === 'horizontal' || direction === 'all') {
631
+ xInertialMotion.value = true;
632
+ offsetX.value = withDecay({
633
+ velocity: e.velocityX / 10,
634
+ rubberBandEffect: outOfBounds,
635
+ clamp: draggableXRange.value
636
+ }, () => {
637
+ xInertialMotion.value = false;
638
+ if (bindchange) {
639
+ runOnJS(handleTriggerChange)({
640
+ x: offsetX.value,
641
+ y: offsetY.value
642
+ });
643
+ }
644
+ });
645
+ }
646
+ if (direction === 'vertical' || direction === 'all') {
647
+ yInertialMotion.value = true;
648
+ offsetY.value = withDecay({
649
+ velocity: e.velocityY / 10,
650
+ rubberBandEffect: outOfBounds,
651
+ clamp: draggableYRange.value
652
+ }, () => {
653
+ yInertialMotion.value = false;
654
+ if (bindchange) {
655
+ runOnJS(handleTriggerChange)({
656
+ x: offsetX.value,
657
+ y: offsetY.value
658
+ });
659
+ }
660
+ });
661
+ }
434
662
  }
435
663
  })
436
664
  .withRef(movableGestureRef);
437
- if (direction === 'horizontal') {
438
- gesturePan.activeOffsetX([-5, 5]).failOffsetY([-5, 5]);
439
- }
440
- else if (direction === 'vertical') {
441
- gesturePan.activeOffsetY([-5, 5]).failOffsetX([-5, 5]);
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
+ }
442
672
  }
443
673
  if (simultaneousHandlers && simultaneousHandlers.length) {
444
674
  gesturePan.simultaneousWithExternalGesture(...simultaneousHandlers);
@@ -446,13 +676,43 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
446
676
  if (waitForHandlers && waitForHandlers.length) {
447
677
  gesturePan.requireExternalGestureToFail(...waitForHandlers);
448
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
+ }
449
708
  return gesturePan;
450
- }, [disabled, direction, inertia, outOfBounds, gestureSwitch.current]);
709
+ }, [disabled, direction, inertia, outOfBounds, scale, scaleMin, scaleMax, animation, gestureSwitch.current, handleScaleUpdate, MovableAreaLayout.scaleArea]);
451
710
  const animatedStyles = useAnimatedStyle(() => {
452
711
  return {
453
712
  transform: [
454
713
  { translateX: offsetX.value },
455
- { translateY: offsetY.value }
714
+ { translateY: offsetY.value },
715
+ { scale: currentScale.value }
456
716
  ]
457
717
  };
458
718
  });
@@ -489,7 +749,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
489
749
  const innerProps = useInnerProps(extendObject({}, filterProps, {
490
750
  ref: nodeRef,
491
751
  onLayout: onLayout,
492
- style: [innerStyle, animatedStyles, layoutStyle]
752
+ style: [{ transformOrigin: 'top left' }, innerStyle, animatedStyles, layoutStyle]
493
753
  }, rewriteCatchEvent()));
494
754
  return createElement(GestureDetector, { gesture: gesture }, createElement(Animated.View, innerProps, wrapChildren(props, {
495
755
  hasVarDec,
@@ -56,6 +56,9 @@ const _RichText = forwardRef((props, ref) => {
56
56
  source: { html: generateHTML(html) },
57
57
  onMessage: (event) => {
58
58
  setWebViewHeight(+event.nativeEvent.data);
59
+ },
60
+ style: {
61
+ backgroundColor: 'transparent'
59
62
  }
60
63
  }));
61
64
  if (hasPositionFixed) {