@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
  */
@@ -30,8 +30,8 @@ import Animated, {
30
30
  withDecay,
31
31
  runOnJS,
32
32
  runOnUI,
33
- useAnimatedReaction,
34
- withSpring
33
+ withSpring,
34
+ withTiming
35
35
  } from 'react-native-reanimated'
36
36
  import { collectDataset, noop } from '@mpxjs/utils'
37
37
 
@@ -43,8 +43,14 @@ interface MovableViewProps {
43
43
  y?: number
44
44
  disabled?: boolean
45
45
  animation?: boolean
46
+ scale?: boolean
47
+ 'scale-min'?: number
48
+ 'scale-max'?: number
49
+ 'scale-value'?: number
46
50
  id?: string
51
+ changeThrottleTime?:number
47
52
  bindchange?: (event: unknown) => void
53
+ bindscale?: (event: unknown) => void
48
54
  bindtouchstart?: (event: GestureTouchEvent) => void
49
55
  catchtouchstart?: (event: GestureTouchEvent) => void
50
56
  bindtouchmove?: (event: GestureTouchEvent) => void
@@ -69,6 +75,7 @@ interface MovableViewProps {
69
75
  'parent-font-size'?: number
70
76
  'parent-width'?: number
71
77
  'parent-height'?: number
78
+ 'disable-event-passthrough'?: boolean
72
79
  }
73
80
 
74
81
  const styles = StyleSheet.create({
@@ -95,6 +102,10 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
95
102
  inertia = false,
96
103
  disabled = false,
97
104
  animation = true,
105
+ scale = false,
106
+ 'scale-min': scaleMin = 0.1,
107
+ 'scale-max': scaleMax = 10,
108
+ 'scale-value': scaleValue = 1,
98
109
  'out-of-bounds': outOfBounds = false,
99
110
  'enable-var': enableVar,
100
111
  'external-var-context': externalVarContext,
@@ -102,9 +113,11 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
102
113
  'parent-width': parentWidth,
103
114
  'parent-height': parentHeight,
104
115
  direction = 'none',
116
+ 'disable-event-passthrough': disableEventPassthrough = false,
105
117
  'simultaneous-handlers': originSimultaneousHandlers = [],
106
118
  'wait-for': waitFor = [],
107
119
  style = {},
120
+ changeThrottleTime = 60,
108
121
  bindtouchstart,
109
122
  catchtouchstart,
110
123
  bindhtouchmove,
@@ -114,7 +127,10 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
114
127
  catchvtouchmove,
115
128
  catchtouchmove,
116
129
  bindtouchend,
117
- catchtouchend
130
+ catchtouchend,
131
+ bindscale,
132
+ bindchange,
133
+ onLayout: propsOnLayout
118
134
  } = props
119
135
 
120
136
  const {
@@ -124,7 +140,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
124
140
  varContextRef,
125
141
  setWidth,
126
142
  setHeight
127
- } = useTransformStyle(Object.assign({}, style, styles.container), { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
143
+ } = useTransformStyle(Object.assign({}, styles.container, style), { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
128
144
 
129
145
  const navigation = useNavigation()
130
146
 
@@ -135,11 +151,14 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
135
151
 
136
152
  const offsetX = useSharedValue(x)
137
153
  const offsetY = useSharedValue(y)
154
+ const currentScale = useSharedValue(1)
155
+ const layoutValue = useSharedValue<any>({})
138
156
 
139
157
  const startPosition = useSharedValue({
140
158
  x: 0,
141
159
  y: 0
142
160
  })
161
+
143
162
  const draggableXRange = useSharedValue<[min: number, max: number]>([0, 0])
144
163
  const draggableYRange = useSharedValue<[min: number, max: number]>([0, 0])
145
164
  const isMoving = useSharedValue(false)
@@ -148,6 +167,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
148
167
  const isFirstTouch = useSharedValue(true)
149
168
  const touchEvent = useSharedValue<string>('')
150
169
  const initialViewPosition = useSharedValue({ x: x || 0, y: y || 0 })
170
+ const lastChangeTime = useSharedValue(0)
151
171
 
152
172
  const MovableAreaLayout = useContext(MovableAreaContext)
153
173
 
@@ -195,6 +215,51 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
195
215
  )
196
216
  }, [])
197
217
 
218
+ const handleTriggerScale = useCallback(({ x, y, scale }: { x: number; y: number; scale: number }) => {
219
+ const { bindscale } = propsRef.current
220
+ if (!bindscale) return
221
+ bindscale(
222
+ getCustomEvent('scale', {}, {
223
+ detail: {
224
+ x,
225
+ y,
226
+ scale
227
+ },
228
+ layoutRef
229
+ }, propsRef.current)
230
+ )
231
+ }, [])
232
+
233
+ const checkBoundaryPosition = useCallback(({ positionX, positionY }: { positionX: number; positionY: number }) => {
234
+ 'worklet'
235
+ let x = positionX
236
+ let y = positionY
237
+ // 计算边界限制
238
+ if (x > draggableXRange.value[1]) {
239
+ x = draggableXRange.value[1]
240
+ } else if (x < draggableXRange.value[0]) {
241
+ x = draggableXRange.value[0]
242
+ }
243
+
244
+ if (y > draggableYRange.value[1]) {
245
+ y = draggableYRange.value[1]
246
+ } else if (y < draggableYRange.value[0]) {
247
+ y = draggableYRange.value[0]
248
+ }
249
+
250
+ return { x, y }
251
+ }, [])
252
+
253
+ // 节流版本的 change 事件触发
254
+ const handleTriggerChangeThrottled = useCallback(({ x, y, type }: { x: number; y: number; type?: string }) => {
255
+ 'worklet'
256
+ const now = Date.now()
257
+ if (now - lastChangeTime.value >= changeThrottleTime) {
258
+ lastChangeTime.value = now
259
+ runOnJS(handleTriggerChange)({ x, y, type })
260
+ }
261
+ }, [changeThrottleTime])
262
+
198
263
  useEffect(() => {
199
264
  runOnUI(() => {
200
265
  if (offsetX.value !== x || offsetY.value !== y) {
@@ -215,7 +280,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
215
280
  })
216
281
  : newY
217
282
  }
218
- if (propsRef.current.bindchange) {
283
+ if (bindchange) {
219
284
  runOnJS(handleTriggerChange)({
220
285
  x: newX,
221
286
  y: newY,
@@ -226,13 +291,224 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
226
291
  })()
227
292
  }, [x, y])
228
293
 
229
- useEffect(() => {
230
- const { width, height } = layoutRef.current
231
- if (width && height) {
232
- resetBoundaryAndCheck({ width, height })
294
+ // 提取通用的缩放边界计算函数
295
+ const calculateScaleBoundaryPosition = useCallback(({
296
+ currentOffsetX,
297
+ currentOffsetY,
298
+ newScale,
299
+ width,
300
+ height
301
+ }: {
302
+ currentOffsetX: number
303
+ currentOffsetY: number
304
+ newScale: number
305
+ width: number
306
+ height: number
307
+ }) => {
308
+ 'worklet'
309
+ const prevScale = currentScale.value
310
+
311
+ // 计算元素当前中心点(在屏幕上的位置)
312
+ const currentCenterX = currentOffsetX + (width * prevScale) / 2
313
+ const currentCenterY = currentOffsetY + (height * prevScale) / 2
314
+
315
+ // 实现中心缩放:保持元素中心点不变
316
+ // 计算缩放后为了保持中心点不变需要的新offset位置
317
+ let newOffsetX = currentCenterX - (width * newScale) / 2
318
+ let newOffsetY = currentCenterY - (height * newScale) / 2
319
+
320
+ // 缩放过程中实时边界检测
321
+ // 计算新的边界范围
322
+ const top = (style.position === 'absolute' && style.top) || 0
323
+ const left = (style.position === 'absolute' && style.left) || 0
324
+ const scaledWidth = width * newScale
325
+ const scaledHeight = height * newScale
326
+
327
+ // 计算新缩放值下的边界限制
328
+ const maxOffsetY = MovableAreaLayout.height - scaledHeight - top
329
+ const maxOffsetX = MovableAreaLayout.width - scaledWidth - left
330
+
331
+ let xMin, xMax, yMin, yMax
332
+
333
+ if (MovableAreaLayout.width < scaledWidth) {
334
+ xMin = maxOffsetX
335
+ xMax = -left
336
+ } else {
337
+ xMin = -left
338
+ xMax = maxOffsetX < 0 ? -left : maxOffsetX
339
+ }
340
+
341
+ if (MovableAreaLayout.height < scaledHeight) {
342
+ yMin = maxOffsetY
343
+ yMax = -top
344
+ } else {
345
+ yMin = -top
346
+ yMax = maxOffsetY < 0 ? -top : maxOffsetY
347
+ }
348
+
349
+ // 应用边界限制
350
+ if (newOffsetX > xMax) {
351
+ newOffsetX = xMax
352
+ } else if (newOffsetX < xMin) {
353
+ newOffsetX = xMin
354
+ }
355
+
356
+ if (newOffsetY > yMax) {
357
+ newOffsetY = yMax
358
+ } else if (newOffsetY < yMin) {
359
+ newOffsetY = yMin
360
+ }
361
+
362
+ return { x: newOffsetX, y: newOffsetY }
363
+ }, [MovableAreaLayout.height, MovableAreaLayout.width, style.position, style.top, style.left])
364
+
365
+ // 提取通用的缩放处理函数
366
+ const handleScaleUpdate = useCallback((scaleInfo: { scale: number }) => {
367
+ 'worklet'
368
+ if (disabled) return
369
+
370
+ // 判断缩放方向并计算新的缩放值
371
+ const isZoomingIn = scaleInfo.scale > 1
372
+ const isZoomingOut = scaleInfo.scale < 1
373
+
374
+ let newScale
375
+ if (isZoomingIn) {
376
+ // 放大:增加缩放值
377
+ newScale = currentScale.value + (scaleInfo.scale - 1) * 0.5
378
+ } else if (isZoomingOut) {
379
+ // 缩小:减少缩放值
380
+ newScale = currentScale.value - (1 - scaleInfo.scale) * 0.5
381
+ } else {
382
+ // 没有缩放变化
383
+ newScale = currentScale.value
384
+ }
385
+
386
+ // 限制缩放值在 scaleMin 和 scaleMax 之间
387
+ newScale = Math.max(scaleMin, Math.min(scaleMax, newScale))
388
+
389
+ // 只有当缩放值真正改变时才调整位置
390
+ if (Math.abs(newScale - currentScale.value) > 0.01) {
391
+ // 获取元素尺寸
392
+ const { width = 0, height = 0 } = layoutValue.value
393
+
394
+ if (width > 0 && height > 0) {
395
+ // 使用通用的边界计算函数
396
+ const { x: newOffsetX, y: newOffsetY } = calculateScaleBoundaryPosition({
397
+ currentOffsetX: offsetX.value,
398
+ currentOffsetY: offsetY.value,
399
+ newScale,
400
+ width,
401
+ height
402
+ })
403
+
404
+ offsetX.value = newOffsetX
405
+ offsetY.value = newOffsetY
406
+
407
+ // 更新缩放值
408
+ currentScale.value = newScale
409
+ }
410
+ } else {
411
+ currentScale.value = newScale
233
412
  }
413
+
414
+ if (bindscale) {
415
+ runOnJS(handleTriggerScale)({
416
+ x: offsetX.value,
417
+ y: offsetY.value,
418
+ scale: newScale
419
+ })
420
+ }
421
+ }, [disabled, scaleMin, scaleMax, bindscale, handleTriggerScale, calculateScaleBoundaryPosition, style.position, style.top, style.left, MovableAreaLayout.height, MovableAreaLayout.width])
422
+
423
+ useEffect(() => {
424
+ runOnUI(() => {
425
+ if (currentScale.value !== scaleValue) {
426
+ // 限制缩放值在 scaleMin 和 scaleMax 之间
427
+ const clampedScale = Math.max(scaleMin, Math.min(scaleMax, scaleValue))
428
+
429
+ // 实现中心缩放的位置补偿
430
+ const { width = 0, height = 0 } = layoutValue.value
431
+ if (width > 0 && height > 0) {
432
+ // 使用通用的边界计算函数
433
+ const { x: newOffsetX, y: newOffsetY } = calculateScaleBoundaryPosition({
434
+ currentOffsetX: offsetX.value,
435
+ currentOffsetY: offsetY.value,
436
+ newScale: clampedScale,
437
+ width,
438
+ height
439
+ })
440
+
441
+ // 同时更新缩放值和位置
442
+ if (animation) {
443
+ currentScale.value = withTiming(clampedScale, {
444
+ duration: 1000
445
+ }, () => {
446
+ handleRestBoundaryAndCheck()
447
+ })
448
+ offsetX.value = withTiming(newOffsetX, { duration: 1000 })
449
+ offsetY.value = withTiming(newOffsetY, { duration: 1000 })
450
+ } else {
451
+ currentScale.value = clampedScale
452
+ offsetX.value = newOffsetX
453
+ offsetY.value = newOffsetY
454
+ handleRestBoundaryAndCheck()
455
+ }
456
+ } else {
457
+ // 如果还没有尺寸信息,只更新缩放值
458
+ if (animation) {
459
+ currentScale.value = withTiming(clampedScale, {
460
+ duration: 1000
461
+ }, () => {
462
+ handleRestBoundaryAndCheck()
463
+ })
464
+ } else {
465
+ currentScale.value = clampedScale
466
+ handleRestBoundaryAndCheck()
467
+ }
468
+ }
469
+
470
+ if (bindscale) {
471
+ runOnJS(handleTriggerScale)({
472
+ x: offsetX.value,
473
+ y: offsetY.value,
474
+ scale: clampedScale
475
+ })
476
+ }
477
+ }
478
+ })()
479
+ }, [scaleValue, scaleMin, scaleMax, animation])
480
+
481
+ useEffect(() => {
482
+ runOnUI(handleRestBoundaryAndCheck)()
234
483
  }, [MovableAreaLayout.height, MovableAreaLayout.width])
235
484
 
485
+ // 生成唯一 ID
486
+ const viewId = useMemo(() => `movable-view-${Date.now()}-${Math.random()}`, [])
487
+
488
+ // 注册到 MovableArea(如果启用了 scale-area)
489
+ useEffect(() => {
490
+ if (MovableAreaLayout.scaleArea && MovableAreaLayout.registerMovableView && MovableAreaLayout.unregisterMovableView) {
491
+ const handleAreaScale = (scaleInfo: { scale: number }) => {
492
+ 'worklet'
493
+ handleScaleUpdate({ scale: scaleInfo.scale })
494
+ }
495
+
496
+ const handleAreaScaleEnd = () => {
497
+ 'worklet'
498
+ handleRestBoundaryAndCheck()
499
+ }
500
+
501
+ MovableAreaLayout.registerMovableView?.(viewId, {
502
+ onScale: scale ? handleAreaScale : noop,
503
+ onScaleEnd: scale ? handleAreaScaleEnd : noop
504
+ })
505
+
506
+ return () => {
507
+ MovableAreaLayout.unregisterMovableView?.(viewId)
508
+ }
509
+ }
510
+ }, [MovableAreaLayout.scaleArea, viewId, scale, handleScaleUpdate])
511
+
236
512
  const getTouchSource = useCallback((offsetX: number, offsetY: number) => {
237
513
  const hasOverBoundary = offsetX < draggableXRange.value[0] || offsetX > draggableXRange.value[1] ||
238
514
  offsetY < draggableYRange.value[0] || offsetY > draggableYRange.value[1]
@@ -255,66 +531,50 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
255
531
  }, [])
256
532
 
257
533
  const setBoundary = useCallback(({ width, height }: { width: number; height: number }) => {
534
+ 'worklet'
258
535
  const top = (style.position === 'absolute' && style.top) || 0
259
536
  const left = (style.position === 'absolute' && style.left) || 0
260
537
 
261
- const scaledWidth = width || 0
262
- const scaledHeight = height || 0
538
+ // 使用左上角缩放,计算offset位置的边界范围
539
+ const currentScaleVal = currentScale.value
540
+ const scaledWidth = (width || 0) * currentScaleVal
541
+ const scaledHeight = (height || 0) * currentScaleVal
263
542
 
264
- const maxY = MovableAreaLayout.height - scaledHeight - top
265
- const maxX = MovableAreaLayout.width - scaledWidth - left
543
+ // offset位置的边界:左上角可以移动的范围
544
+ const maxOffsetY = MovableAreaLayout.height - scaledHeight - top
545
+ const maxOffsetX = MovableAreaLayout.width - scaledWidth - left
266
546
 
267
547
  let xRange: [min: number, max: number]
268
548
  let yRange: [min: number, max: number]
269
549
 
270
550
  if (MovableAreaLayout.width < scaledWidth) {
271
- xRange = [maxX, 0]
551
+ xRange = [maxOffsetX, -left]
272
552
  } else {
273
- xRange = [left === 0 ? 0 : -left, maxX < 0 ? 0 : maxX]
553
+ xRange = [-left, maxOffsetX < 0 ? -left : maxOffsetX]
274
554
  }
275
555
 
276
556
  if (MovableAreaLayout.height < scaledHeight) {
277
- yRange = [maxY, 0]
557
+ yRange = [maxOffsetY, -top]
278
558
  } else {
279
- yRange = [top === 0 ? 0 : -top, maxY < 0 ? 0 : maxY]
559
+ yRange = [-top, maxOffsetY < 0 ? -top : maxOffsetY]
280
560
  }
561
+
281
562
  draggableXRange.value = xRange
282
563
  draggableYRange.value = yRange
283
564
  }, [MovableAreaLayout.height, MovableAreaLayout.width, style.position, style.top, style.left])
284
565
 
285
- const checkBoundaryPosition = useCallback(({ positionX, positionY }: { positionX: number; positionY: number }) => {
566
+ const resetBoundaryAndCheck = ({ width, height }: { width: number; height: number }) => {
286
567
  'worklet'
287
- let x = positionX
288
- let y = positionY
289
- // 计算边界限制
290
- if (x > draggableXRange.value[1]) {
291
- x = draggableXRange.value[1]
292
- } else if (x < draggableXRange.value[0]) {
293
- x = draggableXRange.value[0]
568
+ setBoundary({ width, height })
569
+ const positionX = offsetX.value
570
+ const positionY = offsetY.value
571
+ const { x: newX, y: newY } = checkBoundaryPosition({ positionX, positionY })
572
+ if (positionX !== newX) {
573
+ offsetX.value = newX
294
574
  }
295
-
296
- if (y > draggableYRange.value[1]) {
297
- y = draggableYRange.value[1]
298
- } else if (y < draggableYRange.value[0]) {
299
- y = draggableYRange.value[0]
575
+ if (positionY !== newY) {
576
+ offsetY.value = newY
300
577
  }
301
-
302
- return { x, y }
303
- }, [])
304
-
305
- const resetBoundaryAndCheck = ({ width, height }: { width: number; height: number }) => {
306
- setBoundary({ width, height })
307
- runOnUI(() => {
308
- const positionX = offsetX.value
309
- const positionY = offsetY.value
310
- const { x: newX, y: newY } = checkBoundaryPosition({ positionX, positionY })
311
- if (positionX !== newX) {
312
- offsetX.value = newX
313
- }
314
- if (positionY !== newY) {
315
- offsetY.value = newY
316
- }
317
- })()
318
578
  }
319
579
 
320
580
  const onLayout = (e: LayoutChangeEvent) => {
@@ -327,14 +587,19 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
327
587
  nodeRef.current?.measure((x: number, y: number, width: number, height: number) => {
328
588
  const { y: navigationY = 0 } = navigation?.layout || {}
329
589
  layoutRef.current = { x, y: y - navigationY, width, height, offsetLeft: 0, offsetTop: 0 }
330
- resetBoundaryAndCheck({ width, height })
590
+ // 同时更新 layoutValue,供缩放逻辑使用
591
+ runOnUI(() => {
592
+ layoutValue.value = { width, height }
593
+ resetBoundaryAndCheck({ width: width, height: height })
594
+ })()
331
595
  })
332
- props.onLayout && props.onLayout(e)
596
+ propsOnLayout && propsOnLayout(e)
333
597
  }
334
598
 
335
599
  const extendEvent = useCallback((e: any, type: 'start' | 'move' | 'end') => {
336
600
  const { y: navigationY = 0 } = navigation?.layout || {}
337
601
  const touchArr = [e.changedTouches, e.allTouches]
602
+ const currentProps = propsRef.current
338
603
  touchArr.forEach(touches => {
339
604
  touches && touches.forEach((item: { absoluteX: number; absoluteY: number; pageX: number; pageY: number; clientX: number; clientY: number }) => {
340
605
  item.pageX = item.absoluteX
@@ -346,8 +611,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
346
611
  Object.assign(e, {
347
612
  touches: type === 'end' ? [] : e.allTouches,
348
613
  currentTarget: {
349
- id: props.id || '',
350
- dataset: collectDataset(props),
614
+ id: currentProps.id || '',
615
+ dataset: collectDataset(currentProps),
351
616
  offsetLeft: 0,
352
617
  offsetTop: 0
353
618
  },
@@ -356,12 +621,14 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
356
621
  }, [])
357
622
 
358
623
  const triggerStartOnJS = ({ e }: { e: GestureTouchEvent }) => {
624
+ const { bindtouchstart, catchtouchstart } = propsRef.current
359
625
  extendEvent(e, 'start')
360
626
  bindtouchstart && bindtouchstart(e)
361
627
  catchtouchstart && catchtouchstart(e)
362
628
  }
363
629
 
364
630
  const triggerMoveOnJS = ({ e, hasTouchmove, hasCatchTouchmove, touchEvent }: { e: GestureTouchEvent; hasTouchmove: boolean; hasCatchTouchmove: boolean; touchEvent: string }) => {
631
+ const { bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove } = propsRef.current
365
632
  extendEvent(e, 'move')
366
633
  if (hasTouchmove) {
367
634
  if (touchEvent === 'htouchmove') {
@@ -383,11 +650,20 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
383
650
  }
384
651
 
385
652
  const triggerEndOnJS = ({ e }: { e: GestureTouchEvent }) => {
653
+ const { bindtouchend, catchtouchend } = propsRef.current
386
654
  extendEvent(e, 'end')
387
655
  bindtouchend && bindtouchend(e)
388
656
  catchtouchend && catchtouchend(e)
389
657
  }
390
658
 
659
+ const handleRestBoundaryAndCheck = () => {
660
+ 'worklet'
661
+ const { width, height } = layoutValue.value
662
+ if (width && height) {
663
+ resetBoundaryAndCheck({ width, height })
664
+ }
665
+ }
666
+
391
667
  const gesture = useMemo(() => {
392
668
  const handleTriggerMove = (e: GestureTouchEvent) => {
393
669
  'worklet'
@@ -404,6 +680,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
404
680
  }
405
681
 
406
682
  const gesturePan = Gesture.Pan()
683
+ .minPointers(1)
684
+ .maxPointers(1)
407
685
  .onTouchesDown((e: GestureTouchEvent) => {
408
686
  'worklet'
409
687
  const changedTouches = e.changedTouches[0] || { x: 0, y: 0 }
@@ -454,8 +732,9 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
454
732
  offsetY.value = newY
455
733
  }
456
734
  }
457
- if (propsRef.current.bindchange) {
458
- runOnJS(handleTriggerChange)({
735
+ if (bindchange) {
736
+ // 使用节流版本减少 runOnJS 调用
737
+ handleTriggerChangeThrottled({
459
738
  x: offsetX.value,
460
739
  y: offsetY.value
461
740
  })
@@ -493,55 +772,57 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
493
772
  })
494
773
  : y
495
774
  }
496
- if (propsRef.current.bindchange) {
775
+ if (bindchange) {
497
776
  runOnJS(handleTriggerChange)({
498
777
  x,
499
778
  y
500
779
  })
501
780
  }
502
781
  }
503
- return
504
- }
505
- // 惯性处理
506
- if (direction === 'horizontal' || direction === 'all') {
507
- xInertialMotion.value = true
508
- offsetX.value = withDecay({
509
- velocity: e.velocityX / 10,
510
- rubberBandEffect: outOfBounds,
511
- clamp: draggableXRange.value
512
- }, () => {
513
- xInertialMotion.value = false
514
- if (propsRef.current.bindchange) {
515
- runOnJS(handleTriggerChange)({
516
- x: offsetX.value,
517
- y: offsetY.value
518
- })
519
- }
520
- })
521
- }
522
- if (direction === 'vertical' || direction === 'all') {
523
- yInertialMotion.value = true
524
- offsetY.value = withDecay({
525
- velocity: e.velocityY / 10,
526
- rubberBandEffect: outOfBounds,
527
- clamp: draggableYRange.value
528
- }, () => {
529
- yInertialMotion.value = false
530
- if (propsRef.current.bindchange) {
531
- runOnJS(handleTriggerChange)({
532
- x: offsetX.value,
533
- y: offsetY.value
534
- })
535
- }
536
- })
782
+ } else if (inertia) {
783
+ // 惯性处理
784
+ if (direction === 'horizontal' || direction === 'all') {
785
+ xInertialMotion.value = true
786
+ offsetX.value = withDecay({
787
+ velocity: e.velocityX / 10,
788
+ rubberBandEffect: outOfBounds,
789
+ clamp: draggableXRange.value
790
+ }, () => {
791
+ xInertialMotion.value = false
792
+ if (bindchange) {
793
+ runOnJS(handleTriggerChange)({
794
+ x: offsetX.value,
795
+ y: offsetY.value
796
+ })
797
+ }
798
+ })
799
+ }
800
+ if (direction === 'vertical' || direction === 'all') {
801
+ yInertialMotion.value = true
802
+ offsetY.value = withDecay({
803
+ velocity: e.velocityY / 10,
804
+ rubberBandEffect: outOfBounds,
805
+ clamp: draggableYRange.value
806
+ }, () => {
807
+ yInertialMotion.value = false
808
+ if (bindchange) {
809
+ runOnJS(handleTriggerChange)({
810
+ x: offsetX.value,
811
+ y: offsetY.value
812
+ })
813
+ }
814
+ })
815
+ }
537
816
  }
538
817
  })
539
818
  .withRef(movableGestureRef)
540
819
 
541
- if (direction === 'horizontal') {
542
- gesturePan.activeOffsetX([-5, 5]).failOffsetY([-5, 5])
543
- } else if (direction === 'vertical') {
544
- gesturePan.activeOffsetY([-5, 5]).failOffsetX([-5, 5])
820
+ if (!disableEventPassthrough) {
821
+ if (direction === 'horizontal') {
822
+ gesturePan.activeOffsetX([-5, 5]).failOffsetY([-5, 5])
823
+ } else if (direction === 'vertical') {
824
+ gesturePan.activeOffsetY([-5, 5]).failOffsetX([-5, 5])
825
+ }
545
826
  }
546
827
 
547
828
  if (simultaneousHandlers && simultaneousHandlers.length) {
@@ -551,14 +832,46 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
551
832
  if (waitForHandlers && waitForHandlers.length) {
552
833
  gesturePan.requireExternalGestureToFail(...waitForHandlers)
553
834
  }
835
+
836
+ // 添加缩放手势支持
837
+ if (scale && !MovableAreaLayout.scaleArea) {
838
+ const gesturePinch = Gesture.Pinch()
839
+ .onUpdate((e: any) => {
840
+ 'worklet'
841
+ handleScaleUpdate({ scale: e.scale })
842
+ })
843
+ .onEnd((e: any) => {
844
+ 'worklet'
845
+ if (disabled) return
846
+ // 确保最终缩放值在有效范围内
847
+ const finalScale = Math.max(scaleMin, Math.min(scaleMax, currentScale.value))
848
+ if (finalScale !== currentScale.value) {
849
+ currentScale.value = finalScale
850
+ if (bindscale) {
851
+ runOnJS(handleTriggerScale)({
852
+ x: offsetX.value,
853
+ y: offsetY.value,
854
+ scale: finalScale
855
+ })
856
+ }
857
+ }
858
+ // 缩放结束后重新检查边界
859
+ handleRestBoundaryAndCheck()
860
+ })
861
+
862
+ // 根据手指数量自动区分手势:一指移动,两指缩放
863
+ return Gesture.Exclusive(gesturePan, gesturePinch)
864
+ }
865
+
554
866
  return gesturePan
555
- }, [disabled, direction, inertia, outOfBounds, gestureSwitch.current])
867
+ }, [disabled, direction, inertia, outOfBounds, scale, scaleMin, scaleMax, animation, gestureSwitch.current, handleScaleUpdate, MovableAreaLayout.scaleArea])
556
868
 
557
869
  const animatedStyles = useAnimatedStyle(() => {
558
870
  return {
559
871
  transform: [
560
872
  { translateX: offsetX.value },
561
- { translateY: offsetY.value }
873
+ { translateY: offsetY.value },
874
+ { scale: currentScale.value }
562
875
  ]
563
876
  }
564
877
  })
@@ -605,7 +918,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
605
918
  {
606
919
  ref: nodeRef,
607
920
  onLayout: onLayout,
608
- style: [innerStyle, animatedStyles, layoutStyle]
921
+ style: [{ transformOrigin: 'top left' }, innerStyle, animatedStyles, layoutStyle]
609
922
  },
610
923
  rewriteCatchEvent()
611
924
  )