@mpxjs/webpack-plugin 2.10.7-beta.7 → 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.
@@ -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,9 +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
47
51
  changeThrottleTime?:number
48
52
  bindchange?: (event: unknown) => void
53
+ bindscale?: (event: unknown) => void
49
54
  bindtouchstart?: (event: GestureTouchEvent) => void
50
55
  catchtouchstart?: (event: GestureTouchEvent) => void
51
56
  bindtouchmove?: (event: GestureTouchEvent) => void
@@ -70,6 +75,7 @@ interface MovableViewProps {
70
75
  'parent-font-size'?: number
71
76
  'parent-width'?: number
72
77
  'parent-height'?: number
78
+ 'disable-event-passthrough'?: boolean
73
79
  }
74
80
 
75
81
  const styles = StyleSheet.create({
@@ -96,6 +102,10 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
96
102
  inertia = false,
97
103
  disabled = false,
98
104
  animation = true,
105
+ scale = false,
106
+ 'scale-min': scaleMin = 0.1,
107
+ 'scale-max': scaleMax = 10,
108
+ 'scale-value': scaleValue = 1,
99
109
  'out-of-bounds': outOfBounds = false,
100
110
  'enable-var': enableVar,
101
111
  'external-var-context': externalVarContext,
@@ -103,6 +113,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
103
113
  'parent-width': parentWidth,
104
114
  'parent-height': parentHeight,
105
115
  direction = 'none',
116
+ 'disable-event-passthrough': disableEventPassthrough = false,
106
117
  'simultaneous-handlers': originSimultaneousHandlers = [],
107
118
  'wait-for': waitFor = [],
108
119
  style = {},
@@ -117,7 +128,9 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
117
128
  catchtouchmove,
118
129
  bindtouchend,
119
130
  catchtouchend,
120
- bindchange
131
+ bindscale,
132
+ bindchange,
133
+ onLayout: propsOnLayout
121
134
  } = props
122
135
 
123
136
  const {
@@ -127,7 +140,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
127
140
  varContextRef,
128
141
  setWidth,
129
142
  setHeight
130
- } = useTransformStyle(Object.assign({}, style, styles.container), { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
143
+ } = useTransformStyle(Object.assign({}, styles.container, style), { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
131
144
 
132
145
  const navigation = useNavigation()
133
146
 
@@ -138,6 +151,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
138
151
 
139
152
  const offsetX = useSharedValue(x)
140
153
  const offsetY = useSharedValue(y)
154
+ const currentScale = useSharedValue(1)
155
+ const layoutValue = useSharedValue<any>({})
141
156
 
142
157
  const startPosition = useSharedValue({
143
158
  x: 0,
@@ -200,6 +215,41 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
200
215
  )
201
216
  }, [])
202
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
+
203
253
  // 节流版本的 change 事件触发
204
254
  const handleTriggerChangeThrottled = useCallback(({ x, y, type }: { x: number; y: number; type?: string }) => {
205
255
  'worklet'
@@ -241,13 +291,224 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
241
291
  })()
242
292
  }, [x, y])
243
293
 
244
- useEffect(() => {
245
- const { width, height } = layoutRef.current
246
- if (width && height) {
247
- 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
248
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)()
249
483
  }, [MovableAreaLayout.height, MovableAreaLayout.width])
250
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
+
251
512
  const getTouchSource = useCallback((offsetX: number, offsetY: number) => {
252
513
  const hasOverBoundary = offsetX < draggableXRange.value[0] || offsetX > draggableXRange.value[1] ||
253
514
  offsetY < draggableYRange.value[0] || offsetY > draggableYRange.value[1]
@@ -270,66 +531,50 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
270
531
  }, [])
271
532
 
272
533
  const setBoundary = useCallback(({ width, height }: { width: number; height: number }) => {
534
+ 'worklet'
273
535
  const top = (style.position === 'absolute' && style.top) || 0
274
536
  const left = (style.position === 'absolute' && style.left) || 0
275
537
 
276
- const scaledWidth = width || 0
277
- const scaledHeight = height || 0
538
+ // 使用左上角缩放,计算offset位置的边界范围
539
+ const currentScaleVal = currentScale.value
540
+ const scaledWidth = (width || 0) * currentScaleVal
541
+ const scaledHeight = (height || 0) * currentScaleVal
278
542
 
279
- const maxY = MovableAreaLayout.height - scaledHeight - top
280
- const maxX = MovableAreaLayout.width - scaledWidth - left
543
+ // offset位置的边界:左上角可以移动的范围
544
+ const maxOffsetY = MovableAreaLayout.height - scaledHeight - top
545
+ const maxOffsetX = MovableAreaLayout.width - scaledWidth - left
281
546
 
282
547
  let xRange: [min: number, max: number]
283
548
  let yRange: [min: number, max: number]
284
549
 
285
550
  if (MovableAreaLayout.width < scaledWidth) {
286
- xRange = [maxX, 0]
551
+ xRange = [maxOffsetX, -left]
287
552
  } else {
288
- xRange = [left === 0 ? 0 : -left, maxX < 0 ? 0 : maxX]
553
+ xRange = [-left, maxOffsetX < 0 ? -left : maxOffsetX]
289
554
  }
290
555
 
291
556
  if (MovableAreaLayout.height < scaledHeight) {
292
- yRange = [maxY, 0]
557
+ yRange = [maxOffsetY, -top]
293
558
  } else {
294
- yRange = [top === 0 ? 0 : -top, maxY < 0 ? 0 : maxY]
559
+ yRange = [-top, maxOffsetY < 0 ? -top : maxOffsetY]
295
560
  }
561
+
296
562
  draggableXRange.value = xRange
297
563
  draggableYRange.value = yRange
298
564
  }, [MovableAreaLayout.height, MovableAreaLayout.width, style.position, style.top, style.left])
299
565
 
300
- const checkBoundaryPosition = useCallback(({ positionX, positionY }: { positionX: number; positionY: number }) => {
566
+ const resetBoundaryAndCheck = ({ width, height }: { width: number; height: number }) => {
301
567
  'worklet'
302
- let x = positionX
303
- let y = positionY
304
- // 计算边界限制
305
- if (x > draggableXRange.value[1]) {
306
- x = draggableXRange.value[1]
307
- } else if (x < draggableXRange.value[0]) {
308
- 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
309
574
  }
310
-
311
- if (y > draggableYRange.value[1]) {
312
- y = draggableYRange.value[1]
313
- } else if (y < draggableYRange.value[0]) {
314
- y = draggableYRange.value[0]
575
+ if (positionY !== newY) {
576
+ offsetY.value = newY
315
577
  }
316
-
317
- return { x, y }
318
- }, [])
319
-
320
- const resetBoundaryAndCheck = ({ width, height }: { width: number; height: number }) => {
321
- setBoundary({ width, height })
322
- runOnUI(() => {
323
- const positionX = offsetX.value
324
- const positionY = offsetY.value
325
- const { x: newX, y: newY } = checkBoundaryPosition({ positionX, positionY })
326
- if (positionX !== newX) {
327
- offsetX.value = newX
328
- }
329
- if (positionY !== newY) {
330
- offsetY.value = newY
331
- }
332
- })()
333
578
  }
334
579
 
335
580
  const onLayout = (e: LayoutChangeEvent) => {
@@ -342,14 +587,19 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
342
587
  nodeRef.current?.measure((x: number, y: number, width: number, height: number) => {
343
588
  const { y: navigationY = 0 } = navigation?.layout || {}
344
589
  layoutRef.current = { x, y: y - navigationY, width, height, offsetLeft: 0, offsetTop: 0 }
345
- resetBoundaryAndCheck({ width, height })
590
+ // 同时更新 layoutValue,供缩放逻辑使用
591
+ runOnUI(() => {
592
+ layoutValue.value = { width, height }
593
+ resetBoundaryAndCheck({ width: width, height: height })
594
+ })()
346
595
  })
347
- props.onLayout && props.onLayout(e)
596
+ propsOnLayout && propsOnLayout(e)
348
597
  }
349
598
 
350
599
  const extendEvent = useCallback((e: any, type: 'start' | 'move' | 'end') => {
351
600
  const { y: navigationY = 0 } = navigation?.layout || {}
352
601
  const touchArr = [e.changedTouches, e.allTouches]
602
+ const currentProps = propsRef.current
353
603
  touchArr.forEach(touches => {
354
604
  touches && touches.forEach((item: { absoluteX: number; absoluteY: number; pageX: number; pageY: number; clientX: number; clientY: number }) => {
355
605
  item.pageX = item.absoluteX
@@ -361,8 +611,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
361
611
  Object.assign(e, {
362
612
  touches: type === 'end' ? [] : e.allTouches,
363
613
  currentTarget: {
364
- id: props.id || '',
365
- dataset: collectDataset(props),
614
+ id: currentProps.id || '',
615
+ dataset: collectDataset(currentProps),
366
616
  offsetLeft: 0,
367
617
  offsetTop: 0
368
618
  },
@@ -406,6 +656,14 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
406
656
  catchtouchend && catchtouchend(e)
407
657
  }
408
658
 
659
+ const handleRestBoundaryAndCheck = () => {
660
+ 'worklet'
661
+ const { width, height } = layoutValue.value
662
+ if (width && height) {
663
+ resetBoundaryAndCheck({ width, height })
664
+ }
665
+ }
666
+
409
667
  const gesture = useMemo(() => {
410
668
  const handleTriggerMove = (e: GestureTouchEvent) => {
411
669
  'worklet'
@@ -422,6 +680,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
422
680
  }
423
681
 
424
682
  const gesturePan = Gesture.Pan()
683
+ .minPointers(1)
684
+ .maxPointers(1)
425
685
  .onTouchesDown((e: GestureTouchEvent) => {
426
686
  'worklet'
427
687
  const changedTouches = e.changedTouches[0] || { x: 0, y: 0 }
@@ -557,10 +817,12 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
557
817
  })
558
818
  .withRef(movableGestureRef)
559
819
 
560
- if (direction === 'horizontal') {
561
- gesturePan.activeOffsetX([-5, 5]).failOffsetY([-5, 5])
562
- } else if (direction === 'vertical') {
563
- 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
+ }
564
826
  }
565
827
 
566
828
  if (simultaneousHandlers && simultaneousHandlers.length) {
@@ -570,14 +832,46 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
570
832
  if (waitForHandlers && waitForHandlers.length) {
571
833
  gesturePan.requireExternalGestureToFail(...waitForHandlers)
572
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
+
573
866
  return gesturePan
574
- }, [disabled, direction, inertia, outOfBounds, gestureSwitch.current])
867
+ }, [disabled, direction, inertia, outOfBounds, scale, scaleMin, scaleMax, animation, gestureSwitch.current, handleScaleUpdate, MovableAreaLayout.scaleArea])
575
868
 
576
869
  const animatedStyles = useAnimatedStyle(() => {
577
870
  return {
578
871
  transform: [
579
872
  { translateX: offsetX.value },
580
- { translateY: offsetY.value }
873
+ { translateY: offsetY.value },
874
+ { scale: currentScale.value }
581
875
  ]
582
876
  }
583
877
  })
@@ -624,7 +918,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
624
918
  {
625
919
  ref: nodeRef,
626
920
  onLayout: onLayout,
627
- style: [innerStyle, animatedStyles, layoutStyle]
921
+ style: [{ transformOrigin: 'top left' }, innerStyle, animatedStyles, layoutStyle]
628
922
  },
629
923
  rewriteCatchEvent()
630
924
  )
@@ -74,6 +74,7 @@ interface ScrollViewProps {
74
74
  'wait-for'?: Array<GestureHandler>;
75
75
  'simultaneous-handlers'?: Array<GestureHandler>;
76
76
  'scroll-event-throttle'?:number;
77
+ 'scroll-into-view-offset'?: number;
77
78
  bindscrolltoupper?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
78
79
  bindscrolltolower?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
79
80
  bindscroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
@@ -149,6 +150,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
149
150
  'wait-for': waitFor,
150
151
  'enable-sticky': enableSticky,
151
152
  'scroll-event-throttle': scrollEventThrottle = 0,
153
+ 'scroll-into-view-offset': scrollIntoViewOffset = 0,
152
154
  __selectRef
153
155
  } = props
154
156
 
@@ -185,7 +187,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
185
187
  const initialTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)
186
188
  const intersectionObservers = useContext(IntersectionObserverContext)
187
189
 
188
- const firstScrollIntoViewChange = useRef<boolean>(false)
190
+ const firstScrollIntoViewChange = useRef<boolean>(true)
189
191
 
190
192
  const refreshColor = {
191
193
  black: ['#000'],
@@ -218,7 +220,8 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
218
220
  pagingEnabled,
219
221
  fastDeceleration: false,
220
222
  decelerationDisabled: false,
221
- scrollTo
223
+ scrollTo,
224
+ scrollIntoView: handleScrollIntoView
222
225
  },
223
226
  gestureRef: scrollViewRef
224
227
  })
@@ -257,13 +260,15 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
257
260
 
258
261
  useEffect(() => {
259
262
  if (scrollIntoView && __selectRef) {
260
- if (!firstScrollIntoViewChange.current) {
261
- setTimeout(handleScrollIntoView)
263
+ if (firstScrollIntoViewChange.current) {
264
+ setTimeout(() => {
265
+ handleScrollIntoView(scrollIntoView, { offset: scrollIntoViewOffset, animated: scrollWithAnimation })
266
+ })
262
267
  } else {
263
- handleScrollIntoView()
268
+ handleScrollIntoView(scrollIntoView, { offset: scrollIntoViewOffset, animated: scrollWithAnimation })
264
269
  }
265
270
  }
266
- firstScrollIntoViewChange.current = true
271
+ firstScrollIntoViewChange.current = false
267
272
  }, [scrollIntoView])
268
273
 
269
274
  useEffect(() => {
@@ -286,14 +291,16 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
286
291
  scrollToOffset(left, top, animated)
287
292
  }
288
293
 
289
- function handleScrollIntoView () {
290
- const refs = __selectRef!(`#${scrollIntoView}`, 'node')
294
+ function handleScrollIntoView (selector = '', { offset = 0, animated = true } = {}) {
295
+ const refs = __selectRef!(`#${selector}`, 'node')
291
296
  if (!refs) return
292
297
  const { nodeRef } = refs.getNodeInstance()
293
298
  nodeRef.current?.measureLayout(
294
299
  scrollViewRef.current,
295
300
  (left: number, top: number) => {
296
- scrollToOffset(left, top)
301
+ const adjustedLeft = scrollX ? left + offset : left
302
+ const adjustedTop = scrollY ? top + offset : top
303
+ scrollToOffset(adjustedLeft, adjustedTop, animated)
297
304
  }
298
305
  )
299
306
  }
@@ -171,7 +171,9 @@ const _StickyHeader = forwardRef<HandlerRef<View, StickyHeaderProps>, StickyHead
171
171
  const styles = StyleSheet.create({
172
172
  content: {
173
173
  width: '100%',
174
- zIndex: 10
174
+ zIndex: 10,
175
+ // harmony 需要手动设置 relative, zIndex 才生效
176
+ position: 'relative'
175
177
  }
176
178
  })
177
179