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

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 (61) hide show
  1. package/lib/file-loader.js +1 -1
  2. package/lib/index.js +41 -13
  3. package/lib/platform/json/wx/index.js +43 -26
  4. package/lib/platform/template/wx/component-config/button.js +1 -1
  5. package/lib/platform/template/wx/component-config/fix-component-name.js +2 -2
  6. package/lib/platform/template/wx/component-config/index.js +5 -1
  7. package/lib/platform/template/wx/component-config/input.js +1 -1
  8. package/lib/platform/template/wx/component-config/sticky-header.js +23 -0
  9. package/lib/platform/template/wx/component-config/sticky-section.js +23 -0
  10. package/lib/platform/template/wx/index.js +2 -1
  11. package/lib/react/LoadAsyncChunkModule.js +68 -0
  12. package/lib/react/index.js +3 -1
  13. package/lib/react/processJSON.js +68 -12
  14. package/lib/react/processScript.js +4 -3
  15. package/lib/react/script-helper.js +92 -18
  16. package/lib/runtime/components/react/AsyncContainer.tsx +217 -0
  17. package/lib/runtime/components/react/AsyncSuspense.tsx +81 -0
  18. package/lib/runtime/components/react/context.ts +12 -3
  19. package/lib/runtime/components/react/dist/AsyncContainer.jsx +160 -0
  20. package/lib/runtime/components/react/dist/AsyncSuspense.jsx +68 -0
  21. package/lib/runtime/components/react/dist/context.js +4 -1
  22. package/lib/runtime/components/react/dist/getInnerListeners.js +1 -1
  23. package/lib/runtime/components/react/dist/mpx-button.jsx +2 -2
  24. package/lib/runtime/components/react/dist/mpx-input.jsx +1 -1
  25. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +1 -1
  26. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +55 -40
  27. package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +3 -0
  28. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +17 -6
  29. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +115 -0
  30. package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +45 -0
  31. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +2 -2
  32. package/lib/runtime/components/react/dist/mpx-swiper.jsx +53 -27
  33. package/lib/runtime/components/react/dist/mpx-view.jsx +21 -7
  34. package/lib/runtime/components/react/dist/mpx-web-view.jsx +16 -30
  35. package/lib/runtime/components/react/dist/useAnimationHooks.js +2 -87
  36. package/lib/runtime/components/react/dist/utils.jsx +105 -1
  37. package/lib/runtime/components/react/getInnerListeners.ts +1 -1
  38. package/lib/runtime/components/react/mpx-button.tsx +3 -2
  39. package/lib/runtime/components/react/mpx-input.tsx +1 -1
  40. package/lib/runtime/components/react/mpx-movable-area.tsx +1 -1
  41. package/lib/runtime/components/react/mpx-movable-view.tsx +60 -41
  42. package/lib/runtime/components/react/mpx-rich-text/index.tsx +3 -0
  43. package/lib/runtime/components/react/mpx-scroll-view.tsx +68 -50
  44. package/lib/runtime/components/react/mpx-sticky-header.tsx +179 -0
  45. package/lib/runtime/components/react/mpx-sticky-section.tsx +96 -0
  46. package/lib/runtime/components/react/mpx-swiper-item.tsx +2 -2
  47. package/lib/runtime/components/react/mpx-swiper.tsx +53 -25
  48. package/lib/runtime/components/react/mpx-view.tsx +20 -7
  49. package/lib/runtime/components/react/mpx-web-view.tsx +14 -34
  50. package/lib/runtime/components/react/types/global.d.ts +15 -0
  51. package/lib/runtime/components/react/useAnimationHooks.ts +2 -85
  52. package/lib/runtime/components/react/utils.tsx +93 -1
  53. package/lib/runtime/components/web/mpx-scroll-view.vue +21 -4
  54. package/lib/runtime/components/web/mpx-sticky-header.vue +91 -0
  55. package/lib/runtime/components/web/mpx-sticky-section.vue +15 -0
  56. package/lib/runtime/optionProcessor.js +0 -2
  57. package/lib/template-compiler/compiler.js +2 -2
  58. package/lib/utils/dom-tag-config.js +17 -3
  59. package/lib/web/script-helper.js +1 -1
  60. package/package.json +4 -4
  61. package/LICENSE +0 -433
@@ -44,6 +44,7 @@ interface MovableViewProps {
44
44
  disabled?: boolean
45
45
  animation?: boolean
46
46
  id?: string
47
+ changeThrottleTime?:number
47
48
  bindchange?: (event: unknown) => void
48
49
  bindtouchstart?: (event: GestureTouchEvent) => void
49
50
  catchtouchstart?: (event: GestureTouchEvent) => void
@@ -105,6 +106,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
105
106
  'simultaneous-handlers': originSimultaneousHandlers = [],
106
107
  'wait-for': waitFor = [],
107
108
  style = {},
109
+ changeThrottleTime = 60,
108
110
  bindtouchstart,
109
111
  catchtouchstart,
110
112
  bindhtouchmove,
@@ -114,7 +116,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
114
116
  catchvtouchmove,
115
117
  catchtouchmove,
116
118
  bindtouchend,
117
- catchtouchend
119
+ catchtouchend,
120
+ bindchange
118
121
  } = props
119
122
 
120
123
  const {
@@ -140,6 +143,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
140
143
  x: 0,
141
144
  y: 0
142
145
  })
146
+
143
147
  const draggableXRange = useSharedValue<[min: number, max: number]>([0, 0])
144
148
  const draggableYRange = useSharedValue<[min: number, max: number]>([0, 0])
145
149
  const isMoving = useSharedValue(false)
@@ -148,6 +152,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
148
152
  const isFirstTouch = useSharedValue(true)
149
153
  const touchEvent = useSharedValue<string>('')
150
154
  const initialViewPosition = useSharedValue({ x: x || 0, y: y || 0 })
155
+ const lastChangeTime = useSharedValue(0)
151
156
 
152
157
  const MovableAreaLayout = useContext(MovableAreaContext)
153
158
 
@@ -195,6 +200,16 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
195
200
  )
196
201
  }, [])
197
202
 
203
+ // 节流版本的 change 事件触发
204
+ const handleTriggerChangeThrottled = useCallback(({ x, y, type }: { x: number; y: number; type?: string }) => {
205
+ 'worklet'
206
+ const now = Date.now()
207
+ if (now - lastChangeTime.value >= changeThrottleTime) {
208
+ lastChangeTime.value = now
209
+ runOnJS(handleTriggerChange)({ x, y, type })
210
+ }
211
+ }, [changeThrottleTime])
212
+
198
213
  useEffect(() => {
199
214
  runOnUI(() => {
200
215
  if (offsetX.value !== x || offsetY.value !== y) {
@@ -215,7 +230,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
215
230
  })
216
231
  : newY
217
232
  }
218
- if (propsRef.current.bindchange) {
233
+ if (bindchange) {
219
234
  runOnJS(handleTriggerChange)({
220
235
  x: newX,
221
236
  y: newY,
@@ -325,7 +340,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
325
340
  setHeight(height || 0)
326
341
  }
327
342
  nodeRef.current?.measure((x: number, y: number, width: number, height: number) => {
328
- const { y: navigationY = 0 } = navigation?.layout || {}
343
+ const { top: navigationY = 0 } = navigation?.layout || {}
329
344
  layoutRef.current = { x, y: y - navigationY, width, height, offsetLeft: 0, offsetTop: 0 }
330
345
  resetBoundaryAndCheck({ width, height })
331
346
  })
@@ -333,7 +348,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
333
348
  }
334
349
 
335
350
  const extendEvent = useCallback((e: any, type: 'start' | 'move' | 'end') => {
336
- const { y: navigationY = 0 } = navigation?.layout || {}
351
+ const { top: navigationY = 0 } = navigation?.layout || {}
337
352
  const touchArr = [e.changedTouches, e.allTouches]
338
353
  touchArr.forEach(touches => {
339
354
  touches && touches.forEach((item: { absoluteX: number; absoluteY: number; pageX: number; pageY: number; clientX: number; clientY: number }) => {
@@ -356,12 +371,14 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
356
371
  }, [])
357
372
 
358
373
  const triggerStartOnJS = ({ e }: { e: GestureTouchEvent }) => {
374
+ const { bindtouchstart, catchtouchstart } = propsRef.current
359
375
  extendEvent(e, 'start')
360
376
  bindtouchstart && bindtouchstart(e)
361
377
  catchtouchstart && catchtouchstart(e)
362
378
  }
363
379
 
364
380
  const triggerMoveOnJS = ({ e, hasTouchmove, hasCatchTouchmove, touchEvent }: { e: GestureTouchEvent; hasTouchmove: boolean; hasCatchTouchmove: boolean; touchEvent: string }) => {
381
+ const { bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove } = propsRef.current
365
382
  extendEvent(e, 'move')
366
383
  if (hasTouchmove) {
367
384
  if (touchEvent === 'htouchmove') {
@@ -383,6 +400,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
383
400
  }
384
401
 
385
402
  const triggerEndOnJS = ({ e }: { e: GestureTouchEvent }) => {
403
+ const { bindtouchend, catchtouchend } = propsRef.current
386
404
  extendEvent(e, 'end')
387
405
  bindtouchend && bindtouchend(e)
388
406
  catchtouchend && catchtouchend(e)
@@ -454,8 +472,9 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
454
472
  offsetY.value = newY
455
473
  }
456
474
  }
457
- if (propsRef.current.bindchange) {
458
- runOnJS(handleTriggerChange)({
475
+ if (bindchange) {
476
+ // 使用节流版本减少 runOnJS 调用
477
+ handleTriggerChangeThrottled({
459
478
  x: offsetX.value,
460
479
  y: offsetY.value
461
480
  })
@@ -493,47 +512,47 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
493
512
  })
494
513
  : y
495
514
  }
496
- if (propsRef.current.bindchange) {
515
+ if (bindchange) {
497
516
  runOnJS(handleTriggerChange)({
498
517
  x,
499
518
  y
500
519
  })
501
520
  }
502
521
  }
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
- })
522
+ } else if (inertia) {
523
+ // 惯性处理
524
+ if (direction === 'horizontal' || direction === 'all') {
525
+ xInertialMotion.value = true
526
+ offsetX.value = withDecay({
527
+ velocity: e.velocityX / 10,
528
+ rubberBandEffect: outOfBounds,
529
+ clamp: draggableXRange.value
530
+ }, () => {
531
+ xInertialMotion.value = false
532
+ if (bindchange) {
533
+ runOnJS(handleTriggerChange)({
534
+ x: offsetX.value,
535
+ y: offsetY.value
536
+ })
537
+ }
538
+ })
539
+ }
540
+ if (direction === 'vertical' || direction === 'all') {
541
+ yInertialMotion.value = true
542
+ offsetY.value = withDecay({
543
+ velocity: e.velocityY / 10,
544
+ rubberBandEffect: outOfBounds,
545
+ clamp: draggableYRange.value
546
+ }, () => {
547
+ yInertialMotion.value = false
548
+ if (bindchange) {
549
+ runOnJS(handleTriggerChange)({
550
+ x: offsetX.value,
551
+ y: offsetY.value
552
+ })
553
+ }
554
+ })
555
+ }
537
556
  }
538
557
  })
539
558
  .withRef(movableGestureRef)
@@ -116,6 +116,9 @@ const _RichText = forwardRef<HandlerRef<View, _RichTextProps>, _RichTextProps>((
116
116
  source: { html: generateHTML(html) },
117
117
  onMessage: (event: WebViewMessageEvent) => {
118
118
  setWebViewHeight(+event.nativeEvent.data)
119
+ },
120
+ style: {
121
+ backgroundColor: 'transparent'
119
122
  }
120
123
  })
121
124
  )
@@ -32,7 +32,7 @@
32
32
  * ✔ bindscroll
33
33
  */
34
34
  import { ScrollView, RefreshControl, Gesture, GestureDetector } from 'react-native-gesture-handler'
35
- import { View, NativeSyntheticEvent, NativeScrollEvent, LayoutChangeEvent, ViewStyle } from 'react-native'
35
+ import { View, NativeSyntheticEvent, NativeScrollEvent, LayoutChangeEvent, ViewStyle, Animated as RNAnimated } from 'react-native'
36
36
  import { isValidElement, Children, JSX, ReactNode, RefObject, useRef, useState, useEffect, forwardRef, useContext, useMemo, createElement } from 'react'
37
37
  import Animated, { useAnimatedRef, useSharedValue, withTiming, useAnimatedStyle, runOnJS } from 'react-native-reanimated'
38
38
  import { warn, hasOwn } from '@mpxjs/utils'
@@ -43,48 +43,48 @@ import { IntersectionObserverContext, ScrollViewContext } from './context'
43
43
  import Portal from './mpx-portal'
44
44
 
45
45
  interface ScrollViewProps {
46
- children?: ReactNode
47
- enhanced?: boolean
48
- bounces?: boolean
49
- style?: ViewStyle
50
- scrollEventThrottle?: number
51
- 'scroll-x'?: boolean
52
- 'scroll-y'?: boolean
53
- 'enable-back-to-top'?: boolean
54
- 'show-scrollbar'?: boolean
55
- 'paging-enabled'?: boolean
56
- 'upper-threshold'?: number
57
- 'lower-threshold'?: number
58
- 'scroll-with-animation'?: boolean
59
- 'refresher-triggered'?: boolean
60
- 'refresher-enabled'?: boolean
61
- 'refresher-default-style'?: 'black' | 'white' | 'none'
62
- 'refresher-background'?: string
63
- 'refresher-threshold'?: number
64
- 'scroll-top'?: number
65
- 'scroll-left'?: number
66
- 'enable-offset'?: boolean
67
- 'scroll-into-view'?: string
68
- 'enable-trigger-intersection-observer'?: boolean
69
- 'enable-var'?: boolean
70
- 'external-var-context'?: Record<string, any>
71
- 'parent-font-size'?: number
72
- 'parent-width'?: number
73
- 'parent-height'?: number
74
- 'wait-for'?: Array<GestureHandler>
75
- 'simultaneous-handlers'?: Array<GestureHandler>
76
- 'scroll-event-throttle'?: number
77
- bindscrolltoupper?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
78
- bindscrolltolower?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
79
- bindscroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
80
- bindrefresherrefresh?: (event: NativeSyntheticEvent<unknown>) => void
81
- binddragstart?: (event: NativeSyntheticEvent<DragEvent>) => void
82
- binddragging?: (event: NativeSyntheticEvent<DragEvent>) => void
83
- binddragend?: (event: NativeSyntheticEvent<DragEvent>) => void
84
- bindtouchstart?: (event: NativeSyntheticEvent<TouchEvent>) => void
85
- bindtouchmove?: (event: NativeSyntheticEvent<TouchEvent>) => void
86
- bindtouchend?: (event: NativeSyntheticEvent<TouchEvent>) => void
87
- bindscrollend?: (event: NativeSyntheticEvent<TouchEvent>) => void
46
+ children?: ReactNode;
47
+ enhanced?: boolean;
48
+ bounces?: boolean;
49
+ style?: ViewStyle;
50
+ 'scroll-x'?: boolean;
51
+ 'scroll-y'?: boolean;
52
+ 'enable-back-to-top'?: boolean;
53
+ 'show-scrollbar'?: boolean;
54
+ 'paging-enabled'?: boolean;
55
+ 'upper-threshold'?: number;
56
+ 'lower-threshold'?: number;
57
+ 'scroll-with-animation'?: boolean;
58
+ 'refresher-triggered'?: boolean;
59
+ 'refresher-enabled'?: boolean;
60
+ 'refresher-default-style'?: 'black' | 'white' | 'none';
61
+ 'refresher-background'?: string;
62
+ 'refresher-threshold'?: number;
63
+ 'scroll-top'?: number;
64
+ 'scroll-left'?: number;
65
+ 'enable-offset'?: boolean;
66
+ 'scroll-into-view'?: string;
67
+ 'enable-trigger-intersection-observer'?: boolean;
68
+ 'enable-var'?: boolean;
69
+ 'external-var-context'?: Record<string, any>;
70
+ 'parent-font-size'?: number;
71
+ 'parent-width'?: number;
72
+ 'parent-height'?: number;
73
+ 'enable-sticky'?: boolean;
74
+ 'wait-for'?: Array<GestureHandler>;
75
+ 'simultaneous-handlers'?: Array<GestureHandler>;
76
+ 'scroll-event-throttle'?:number;
77
+ bindscrolltoupper?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
78
+ bindscrolltolower?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
79
+ bindscroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
80
+ bindrefresherrefresh?: (event: NativeSyntheticEvent<unknown>) => void;
81
+ binddragstart?: (event: NativeSyntheticEvent<DragEvent>) => void;
82
+ binddragging?: (event: NativeSyntheticEvent<DragEvent>) => void;
83
+ binddragend?: (event: NativeSyntheticEvent<DragEvent>) => void;
84
+ bindtouchstart?: (event: NativeSyntheticEvent<TouchEvent>) => void;
85
+ bindtouchmove?: (event: NativeSyntheticEvent<TouchEvent>) => void;
86
+ bindtouchend?: (event: NativeSyntheticEvent<TouchEvent>) => void;
87
+ bindscrollend?: (event: NativeSyntheticEvent<TouchEvent>) => void;
88
88
  __selectRef?: (selector: string, nodeType: 'node' | 'component', all?: boolean) => HandlerRef<any, any>
89
89
  }
90
90
  type ScrollAdditionalProps = {
@@ -109,6 +109,8 @@ type ScrollAdditionalProps = {
109
109
  onMomentumScrollEnd?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
110
110
  }
111
111
 
112
+ const AnimatedScrollView = RNAnimated.createAnimatedComponent(ScrollView) as React.ComponentType<any>
113
+
112
114
  const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, ScrollViewProps>((scrollViewProps: ScrollViewProps = {}, ref): JSX.Element => {
113
115
  const { textProps, innerProps: props = {} } = splitProps(scrollViewProps)
114
116
  const {
@@ -145,10 +147,13 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
145
147
  'parent-height': parentHeight,
146
148
  'simultaneous-handlers': originSimultaneousHandlers,
147
149
  'wait-for': waitFor,
150
+ 'enable-sticky': enableSticky,
148
151
  'scroll-event-throttle': scrollEventThrottle = 0,
149
152
  __selectRef
150
153
  } = props
151
154
 
155
+ const scrollOffset = useRef(new RNAnimated.Value(0)).current
156
+
152
157
  const simultaneousHandlers = flatGesture(originSimultaneousHandlers)
153
158
  const waitForHandlers = flatGesture(waitFor)
154
159
 
@@ -218,14 +223,15 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
218
223
  gestureRef: scrollViewRef
219
224
  })
220
225
 
226
+ const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef, onLayout })
227
+
221
228
  const contextValue = useMemo(() => {
222
229
  return {
223
- gestureRef: scrollViewRef
230
+ gestureRef: scrollViewRef,
231
+ scrollOffset
224
232
  }
225
233
  }, [])
226
234
 
227
- const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef, onLayout })
228
-
229
235
  const hasRefresherLayoutRef = useRef(false)
230
236
 
231
237
  // layout 完成前先隐藏,避免安卓闪烁问题
@@ -487,6 +493,16 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
487
493
  updateIntersection()
488
494
  }
489
495
 
496
+ const scrollHandler = RNAnimated.event(
497
+ [{ nativeEvent: { contentOffset: { y: scrollOffset } } }],
498
+ {
499
+ useNativeDriver: true,
500
+ listener: (event: NativeSyntheticEvent<NativeScrollEvent>) => {
501
+ onScroll(event)
502
+ }
503
+ }
504
+ )
505
+
490
506
  function onScrollDragStart (e: NativeSyntheticEvent<NativeScrollEvent>) {
491
507
  hasCallScrollToLower.current = false
492
508
  hasCallScrollToUpper.current = false
@@ -661,7 +677,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
661
677
  scrollEnabled: !enableScroll ? false : !!(scrollX || scrollY),
662
678
  bounces: false,
663
679
  ref: scrollViewRef,
664
- onScroll: onScroll,
680
+ onScroll: enableSticky ? scrollHandler : onScroll,
665
681
  onContentSizeChange: onContentSizeChange,
666
682
  bindtouchstart: ((enhanced && binddragstart) || bindtouchstart) && onScrollTouchStart,
667
683
  bindtouchmove: ((enhanced && binddragging) || bindtouchmove) && onScrollTouchMove,
@@ -716,11 +732,13 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
716
732
  'bindrefresherrefresh'
717
733
  ], { layoutRef })
718
734
 
735
+ const ScrollViewComponent = enableSticky ? AnimatedScrollView : ScrollView
736
+
719
737
  const withRefresherScrollView = createElement(
720
738
  GestureDetector,
721
739
  { gesture: panGesture },
722
740
  createElement(
723
- ScrollView,
741
+ ScrollViewComponent,
724
742
  innerProps,
725
743
  createElement(
726
744
  Animated.View,
@@ -748,8 +766,8 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
748
766
  )
749
767
 
750
768
  const commonScrollView = createElement(
751
- ScrollView,
752
- extendObject(innerProps, {
769
+ ScrollViewComponent,
770
+ extendObject({}, innerProps, {
753
771
  refreshControl: refresherEnabled
754
772
  ? createElement(RefreshControl, extendObject({
755
773
  progressBackgroundColor: refresherBackground,
@@ -0,0 +1,179 @@
1
+ import { useEffect, useRef, useContext, forwardRef, useMemo, createElement, ReactNode, useId } from 'react'
2
+ import { Animated, StyleSheet, View, NativeSyntheticEvent, ViewStyle, LayoutChangeEvent, useAnimatedValue } from 'react-native'
3
+ import { ScrollViewContext, StickyContext } from './context'
4
+ import useNodesRef, { HandlerRef } from './useNodesRef'
5
+ import { splitProps, splitStyle, useTransformStyle, wrapChildren, useLayout, extendObject } from './utils'
6
+ import { error } from '@mpxjs/utils'
7
+ import useInnerProps, { getCustomEvent } from './getInnerListeners'
8
+
9
+ interface StickyHeaderProps {
10
+ children?: ReactNode;
11
+ style?: ViewStyle;
12
+ padding?: [number, number, number, number];
13
+ 'offset-top'?: number;
14
+ 'enable-var'?: boolean;
15
+ 'external-var-context'?: Record<string, any>;
16
+ 'parent-font-size'?: number;
17
+ 'parent-width'?: number;
18
+ 'parent-height'?: number;
19
+ bindstickontopchange?: (e: NativeSyntheticEvent<unknown>) => void;
20
+ }
21
+
22
+ const _StickyHeader = forwardRef<HandlerRef<View, StickyHeaderProps>, StickyHeaderProps>((stickyHeaderProps: StickyHeaderProps = {}, ref): JSX.Element => {
23
+ const { textProps, innerProps: props = {} } = splitProps(stickyHeaderProps)
24
+ const {
25
+ style,
26
+ bindstickontopchange,
27
+ padding = [0, 0, 0, 0],
28
+ 'offset-top': offsetTop = 0,
29
+ 'enable-var': enableVar,
30
+ 'external-var-context': externalVarContext,
31
+ 'parent-font-size': parentFontSize,
32
+ 'parent-width': parentWidth,
33
+ 'parent-height': parentHeight
34
+ } = props
35
+
36
+ const scrollViewContext = useContext(ScrollViewContext)
37
+ const stickyContext = useContext(StickyContext)
38
+ const { scrollOffset } = scrollViewContext
39
+ const { registerStickyHeader, unregisterStickyHeader } = stickyContext
40
+ const headerRef = useRef<View>(null)
41
+ const isStickOnTopRef = useRef(false)
42
+ const id = useId()
43
+
44
+ const {
45
+ normalStyle,
46
+ hasVarDec,
47
+ varContextRef,
48
+ hasSelfPercent,
49
+ setWidth,
50
+ setHeight
51
+ } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
52
+
53
+ const { layoutRef, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: headerRef, onLayout })
54
+
55
+ const { textStyle, innerStyle = {} } = splitStyle(normalStyle)
56
+
57
+ const headerTopAnimated = useAnimatedValue(0)
58
+ // harmony animatedValue 不支持通过 _value 访问
59
+ const headerTopRef = useRef(0)
60
+
61
+ useEffect(() => {
62
+ registerStickyHeader({ key: id, updatePosition })
63
+ return () => {
64
+ unregisterStickyHeader(id)
65
+ }
66
+ }, [])
67
+
68
+ function updatePosition () {
69
+ if (headerRef.current) {
70
+ const scrollViewRef = scrollViewContext.gestureRef
71
+ if (scrollViewRef && scrollViewRef.current) {
72
+ headerRef.current.measureLayout(
73
+ scrollViewRef.current,
74
+ (left: number, top: number) => {
75
+ Animated.timing(headerTopAnimated, {
76
+ toValue: top,
77
+ duration: 0,
78
+ useNativeDriver: true
79
+ }).start()
80
+ headerTopRef.current = top
81
+ }
82
+ )
83
+ } else {
84
+ error('StickyHeader measureLayout error: scrollViewRef is not a valid native component reference')
85
+ }
86
+ }
87
+ }
88
+
89
+ function onLayout (e: LayoutChangeEvent) {
90
+ updatePosition()
91
+ }
92
+
93
+ useNodesRef(props, ref, headerRef, {
94
+ style: normalStyle
95
+ })
96
+
97
+ useEffect(() => {
98
+ if (!bindstickontopchange) return
99
+
100
+ const listener = scrollOffset.addListener((state: { value: number }) => {
101
+ const currentScrollValue = state.value
102
+ const newIsStickOnTop = currentScrollValue > headerTopRef.current
103
+ if (newIsStickOnTop !== isStickOnTopRef.current) {
104
+ isStickOnTopRef.current = newIsStickOnTop
105
+ bindstickontopchange(
106
+ getCustomEvent('stickontopchange', {}, {
107
+ detail: {
108
+ isStickOnTop: newIsStickOnTop
109
+ },
110
+ layoutRef
111
+ }, props))
112
+ }
113
+ })
114
+
115
+ return () => {
116
+ scrollOffset.removeListener(listener)
117
+ }
118
+ }, [])
119
+
120
+ const animatedStyle = useMemo(() => {
121
+ const translateY = Animated.subtract(scrollOffset, headerTopAnimated).interpolate({
122
+ inputRange: [0, 1],
123
+ outputRange: [0, 1],
124
+ extrapolateLeft: 'clamp',
125
+ extrapolateRight: 'extend'
126
+ })
127
+
128
+ const finalTranslateY = offsetTop === 0
129
+ ? translateY
130
+ : Animated.add(
131
+ translateY,
132
+ Animated.subtract(scrollOffset, headerTopAnimated).interpolate({
133
+ inputRange: [0, 1],
134
+ outputRange: [0, offsetTop],
135
+ extrapolate: 'clamp'
136
+ })
137
+ )
138
+
139
+ return {
140
+ transform: [{ translateY: finalTranslateY }]
141
+ }
142
+ }, [scrollOffset, headerTopAnimated, offsetTop])
143
+
144
+ const innerProps = useInnerProps(extendObject({}, props, {
145
+ ref: headerRef,
146
+ style: extendObject({}, styles.content, innerStyle, animatedStyle, {
147
+ paddingTop: padding[0] || 0,
148
+ paddingRight: padding[1] || 0,
149
+ paddingBottom: padding[2] || 0,
150
+ paddingLeft: padding[3] || 0
151
+ })
152
+ }, layoutProps), [], { layoutRef })
153
+
154
+ return (
155
+ createElement(
156
+ Animated.View,
157
+ innerProps,
158
+ wrapChildren(
159
+ props,
160
+ {
161
+ hasVarDec,
162
+ varContext: varContextRef.current,
163
+ textStyle,
164
+ textProps
165
+ }
166
+ )
167
+ )
168
+ )
169
+ })
170
+
171
+ const styles = StyleSheet.create({
172
+ content: {
173
+ width: '100%',
174
+ zIndex: 10
175
+ }
176
+ })
177
+
178
+ _StickyHeader.displayName = 'MpxStickyHeader'
179
+ export default _StickyHeader
@@ -0,0 +1,96 @@
1
+
2
+ import { useRef, forwardRef, createElement, ReactNode, useCallback, useMemo } from 'react'
3
+ import { View, ViewStyle } from 'react-native'
4
+ import useNodesRef, { HandlerRef } from './useNodesRef'
5
+ import { splitProps, splitStyle, useTransformStyle, wrapChildren, useLayout, extendObject } from './utils'
6
+ import { StickyContext } from './context'
7
+ import useInnerProps from './getInnerListeners'
8
+
9
+ interface StickySectionProps {
10
+ children?: ReactNode;
11
+ style?: ViewStyle;
12
+ 'offset-top'?: number;
13
+ 'enable-var'?: boolean;
14
+ 'external-var-context'?: Record<string, any>;
15
+ 'parent-font-size'?: number;
16
+ 'parent-width'?: number;
17
+ 'parent-height'?: number;
18
+ }
19
+
20
+ const _StickySection = forwardRef<HandlerRef<View, StickySectionProps>, StickySectionProps>((stickySectionProps: StickySectionProps = {}, ref): JSX.Element => {
21
+ const { textProps, innerProps: props = {} } = splitProps(stickySectionProps)
22
+ const {
23
+ style,
24
+ 'enable-var': enableVar,
25
+ 'external-var-context': externalVarContext,
26
+ 'parent-font-size': parentFontSize,
27
+ 'parent-width': parentWidth,
28
+ 'parent-height': parentHeight
29
+ } = props
30
+ const sectionRef = useRef<View>(null)
31
+
32
+ const {
33
+ normalStyle,
34
+ hasVarDec,
35
+ varContextRef,
36
+ hasSelfPercent,
37
+ setWidth,
38
+ setHeight
39
+ } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
40
+
41
+ const { layoutRef, layoutProps, layoutStyle } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: sectionRef, onLayout })
42
+
43
+ const { textStyle, innerStyle = {} } = splitStyle(normalStyle)
44
+
45
+ const stickyHeaders = useRef<Map<string, any>>(new Map())
46
+
47
+ const registerStickyHeader = useCallback((item: { id: string, updatePosition: Function }) => {
48
+ stickyHeaders.current.set(item.id, item)
49
+ }, [])
50
+
51
+ const unregisterStickyHeader = useCallback((id: string) => {
52
+ stickyHeaders.current.delete(id)
53
+ }, [])
54
+
55
+ const contextValue = useMemo(() => ({
56
+ registerStickyHeader,
57
+ unregisterStickyHeader
58
+ }), [])
59
+
60
+ useNodesRef(props, ref, sectionRef, {
61
+ style: normalStyle
62
+ })
63
+
64
+ function onLayout () {
65
+ stickyHeaders.current.forEach(item => {
66
+ item.updatePosition()
67
+ })
68
+ }
69
+
70
+ const innerProps = useInnerProps(extendObject({}, props, {
71
+ style: extendObject(innerStyle, layoutStyle),
72
+ ref: sectionRef
73
+ }, layoutProps), [], { layoutRef })
74
+
75
+ return (
76
+ createElement(
77
+ View,
78
+ innerProps,
79
+ createElement(
80
+ StickyContext.Provider,
81
+ { value: contextValue },
82
+ wrapChildren(
83
+ props,
84
+ {
85
+ hasVarDec,
86
+ varContext: varContextRef.current,
87
+ textStyle,
88
+ textProps
89
+ }
90
+ )
91
+ ))
92
+ )
93
+ })
94
+
95
+ _StickySection.displayName = 'MpxStickySection'
96
+ export default _StickySection