@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
@@ -0,0 +1,68 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { DefaultFallback, DefaultLoading, PageWrapper } from './AsyncContainer';
3
+ const asyncChunkMap = new Map();
4
+ const AsyncSuspense = ({ type, props, chunkName, request, loading, fallback, getChildren }) => {
5
+ const [status, setStatus] = useState('pending');
6
+ const loaded = asyncChunkMap.has(request);
7
+ const [, setKey] = useState(0);
8
+ const chunkPromise = useRef(null);
9
+ const reloadPage = useCallback(() => {
10
+ setKey((preV) => preV + 1);
11
+ console.log('[mpxAsyncSuspense]: reload page');
12
+ setStatus('pending');
13
+ }, []);
14
+ useEffect(() => {
15
+ if (!loaded && status === 'pending') {
16
+ // todo 清楚副作用?
17
+ console.log('the current :', chunkPromise.current);
18
+ chunkPromise.current
19
+ .then((m) => {
20
+ console.log('[mpxAsyncSuspense]: load sucess');
21
+ asyncChunkMap.set(request, m.__esModule ? m.default : m);
22
+ setStatus('loaded');
23
+ })
24
+ .catch((e) => {
25
+ if (type === 'component') {
26
+ console.log(11111, e);
27
+ global.onLazyLoadError({
28
+ type: 'subpackage',
29
+ subpackage: [chunkName],
30
+ errMsg: `loadSubpackage: ${e.type}`
31
+ });
32
+ }
33
+ console.log('[mpxAsyncSuspense]: load eror', e);
34
+ chunkPromise.current = null;
35
+ setStatus('error');
36
+ });
37
+ }
38
+ });
39
+ if (loaded) {
40
+ const Comp = asyncChunkMap.get(request);
41
+ return <Comp {...props}></Comp>;
42
+ }
43
+ else if (status === 'error') {
44
+ console.log('the status is:', status);
45
+ if (type === 'page') {
46
+ const Fallback = fallback || DefaultFallback;
47
+ return <><PageWrapper><Fallback onReload={reloadPage}></Fallback></PageWrapper></>;
48
+ }
49
+ else {
50
+ const Fallback = loading;
51
+ return <Fallback {...props}></Fallback>;
52
+ }
53
+ }
54
+ else {
55
+ if (!chunkPromise.current) {
56
+ chunkPromise.current = getChildren();
57
+ }
58
+ if (type === 'page') {
59
+ const Fallback = loading || DefaultLoading;
60
+ return <PageWrapper><Fallback /></PageWrapper>;
61
+ }
62
+ else {
63
+ const Fallback = loading;
64
+ return <Fallback {...props}></Fallback>;
65
+ }
66
+ }
67
+ };
68
+ export default AsyncSuspense;
@@ -1,4 +1,6 @@
1
1
  import { createContext } from 'react';
2
+ import { Animated } from 'react-native';
3
+ import { noop } from '@mpxjs/utils';
2
4
  export const MovableAreaContext = createContext({ width: 0, height: 0 });
3
5
  export const FormContext = createContext(null);
4
6
  export const CheckboxGroupContext = createContext(null);
@@ -10,5 +12,6 @@ export const IntersectionObserverContext = createContext(null);
10
12
  export const RouteContext = createContext(null);
11
13
  export const SwiperContext = createContext({});
12
14
  export const KeyboardAvoidContext = createContext(null);
13
- export const ScrollViewContext = createContext({ gestureRef: null });
15
+ export const ScrollViewContext = createContext({ gestureRef: null, scrollOffset: new Animated.Value(0) });
14
16
  export const PortalContext = createContext(null);
17
+ export const StickyContext = createContext({ registerStickyHeader: noop, unregisterStickyHeader: noop });
@@ -8,7 +8,7 @@ const globalEventState = {
8
8
  const getTouchEvent = (type, event, config) => {
9
9
  const { navigation, propsRef, layoutRef } = config;
10
10
  const props = propsRef.current;
11
- const { y: navigationY = 0 } = navigation?.layout || {};
11
+ const { top: navigationY = 0 } = navigation?.layout || {};
12
12
  const nativeEvent = event.nativeEvent;
13
13
  const { timestamp, pageX, pageY, touches, changedTouches } = nativeEvent;
14
14
  const { id } = props;
@@ -35,7 +35,7 @@
35
35
  * ✔ bindtap
36
36
  */
37
37
  import { createElement, useEffect, useRef, forwardRef, useContext } from 'react';
38
- import { View, StyleSheet, Animated, Easing } from 'react-native';
38
+ import { View, StyleSheet, Animated, Easing, useAnimatedValue } from 'react-native';
39
39
  import { warn } from '@mpxjs/utils';
40
40
  import { GestureDetector } from 'react-native-gesture-handler';
41
41
  import { getCurrentPage, splitProps, splitStyle, useLayout, useTransformStyle, wrapChildren, extendObject, useHover } from './utils';
@@ -104,7 +104,7 @@ const timer = (data, time = 3000) => new Promise((resolve) => {
104
104
  }, time);
105
105
  });
106
106
  const Loading = ({ alone = false }) => {
107
- const image = useRef(new Animated.Value(0)).current;
107
+ const image = useAnimatedValue(0);
108
108
  const rotate = image.interpolate({
109
109
  inputRange: [0, 1],
110
110
  outputRange: ['0deg', '360deg']
@@ -91,7 +91,7 @@ const Input = forwardRef((props, ref) => {
91
91
  });
92
92
  const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef });
93
93
  useEffect(() => {
94
- if (inputValue !== value) {
94
+ if (value !== tmpValue.current) {
95
95
  const parsed = parseValue(value);
96
96
  tmpValue.current = parsed;
97
97
  setInputValue(parsed);
@@ -21,7 +21,7 @@ const _MovableArea = forwardRef((props, ref) => {
21
21
  }), [normalStyle.width, normalStyle.height]);
22
22
  const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: movableViewRef });
23
23
  const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
24
- style: extendObject({ height: contextValue.height, width: contextValue.width, overflow: 'hidden' }, normalStyle, layoutStyle),
24
+ style: extendObject({ height: contextValue.height, width: contextValue.width }, normalStyle, layoutStyle),
25
25
  ref: movableViewRef
26
26
  }), [], { layoutRef });
27
27
  let movableComponent = createElement(MovableAreaContext.Provider, { value: contextValue }, createElement(View, innerProps, wrapChildren(props, {
@@ -41,7 +41,7 @@ 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;
44
+ const { x = 0, y = 0, inertia = false, disabled = false, animation = true, 'out-of-bounds': outOfBounds = false, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, direction = 'none', 'simultaneous-handlers': originSimultaneousHandlers = [], 'wait-for': waitFor = [], style = {}, changeThrottleTime = 60, bindtouchstart, catchtouchstart, bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove, bindtouchend, catchtouchend, bindchange } = props;
45
45
  const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(Object.assign({}, style, styles.container), { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
46
46
  const navigation = useNavigation();
47
47
  const prevSimultaneousHandlersRef = useRef(originSimultaneousHandlers || []);
@@ -62,6 +62,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
62
62
  const isFirstTouch = useSharedValue(true);
63
63
  const touchEvent = useSharedValue('');
64
64
  const initialViewPosition = useSharedValue({ x: x || 0, y: y || 0 });
65
+ const lastChangeTime = useSharedValue(0);
65
66
  const MovableAreaLayout = useContext(MovableAreaContext);
66
67
  const simultaneousHandlers = flatGesture(originSimultaneousHandlers);
67
68
  const waitForHandlers = flatGesture(waitFor);
@@ -99,6 +100,15 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
99
100
  layoutRef
100
101
  }, propsRef.current));
101
102
  }, []);
103
+ // 节流版本的 change 事件触发
104
+ const handleTriggerChangeThrottled = useCallback(({ x, y, type }) => {
105
+ 'worklet';
106
+ const now = Date.now();
107
+ if (now - lastChangeTime.value >= changeThrottleTime) {
108
+ lastChangeTime.value = now;
109
+ runOnJS(handleTriggerChange)({ x, y, type });
110
+ }
111
+ }, [changeThrottleTime]);
102
112
  useEffect(() => {
103
113
  runOnUI(() => {
104
114
  if (offsetX.value !== x || offsetY.value !== y) {
@@ -119,7 +129,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
119
129
  })
120
130
  : newY;
121
131
  }
122
- if (propsRef.current.bindchange) {
132
+ if (bindchange) {
123
133
  runOnJS(handleTriggerChange)({
124
134
  x: newX,
125
135
  y: newY,
@@ -223,14 +233,14 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
223
233
  setHeight(height || 0);
224
234
  }
225
235
  nodeRef.current?.measure((x, y, width, height) => {
226
- const { y: navigationY = 0 } = navigation?.layout || {};
236
+ const { top: navigationY = 0 } = navigation?.layout || {};
227
237
  layoutRef.current = { x, y: y - navigationY, width, height, offsetLeft: 0, offsetTop: 0 };
228
238
  resetBoundaryAndCheck({ width, height });
229
239
  });
230
240
  props.onLayout && props.onLayout(e);
231
241
  };
232
242
  const extendEvent = useCallback((e, type) => {
233
- const { y: navigationY = 0 } = navigation?.layout || {};
243
+ const { top: navigationY = 0 } = navigation?.layout || {};
234
244
  const touchArr = [e.changedTouches, e.allTouches];
235
245
  touchArr.forEach(touches => {
236
246
  touches && touches.forEach((item) => {
@@ -252,11 +262,13 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
252
262
  });
253
263
  }, []);
254
264
  const triggerStartOnJS = ({ e }) => {
265
+ const { bindtouchstart, catchtouchstart } = propsRef.current;
255
266
  extendEvent(e, 'start');
256
267
  bindtouchstart && bindtouchstart(e);
257
268
  catchtouchstart && catchtouchstart(e);
258
269
  };
259
270
  const triggerMoveOnJS = ({ e, hasTouchmove, hasCatchTouchmove, touchEvent }) => {
271
+ const { bindhtouchmove, bindvtouchmove, bindtouchmove, catchhtouchmove, catchvtouchmove, catchtouchmove } = propsRef.current;
260
272
  extendEvent(e, 'move');
261
273
  if (hasTouchmove) {
262
274
  if (touchEvent === 'htouchmove') {
@@ -278,6 +290,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
278
290
  }
279
291
  };
280
292
  const triggerEndOnJS = ({ e }) => {
293
+ const { bindtouchend, catchtouchend } = propsRef.current;
281
294
  extendEvent(e, 'end');
282
295
  bindtouchend && bindtouchend(e);
283
296
  catchtouchend && catchtouchend(e);
@@ -350,8 +363,9 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
350
363
  offsetY.value = newY;
351
364
  }
352
365
  }
353
- if (propsRef.current.bindchange) {
354
- runOnJS(handleTriggerChange)({
366
+ if (bindchange) {
367
+ // 使用节流版本减少 runOnJS 调用
368
+ handleTriggerChangeThrottled({
355
369
  x: offsetX.value,
356
370
  y: offsetY.value
357
371
  });
@@ -390,47 +404,48 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
390
404
  })
391
405
  : y;
392
406
  }
393
- if (propsRef.current.bindchange) {
407
+ if (bindchange) {
394
408
  runOnJS(handleTriggerChange)({
395
409
  x,
396
410
  y
397
411
  });
398
412
  }
399
413
  }
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
414
  }
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
- });
415
+ else if (inertia) {
416
+ // 惯性处理
417
+ if (direction === 'horizontal' || direction === 'all') {
418
+ xInertialMotion.value = true;
419
+ offsetX.value = withDecay({
420
+ velocity: e.velocityX / 10,
421
+ rubberBandEffect: outOfBounds,
422
+ clamp: draggableXRange.value
423
+ }, () => {
424
+ xInertialMotion.value = false;
425
+ if (bindchange) {
426
+ runOnJS(handleTriggerChange)({
427
+ x: offsetX.value,
428
+ y: offsetY.value
429
+ });
430
+ }
431
+ });
432
+ }
433
+ if (direction === 'vertical' || direction === 'all') {
434
+ yInertialMotion.value = true;
435
+ offsetY.value = withDecay({
436
+ velocity: e.velocityY / 10,
437
+ rubberBandEffect: outOfBounds,
438
+ clamp: draggableYRange.value
439
+ }, () => {
440
+ yInertialMotion.value = false;
441
+ if (bindchange) {
442
+ runOnJS(handleTriggerChange)({
443
+ x: offsetX.value,
444
+ y: offsetY.value
445
+ });
446
+ }
447
+ });
448
+ }
434
449
  }
435
450
  })
436
451
  .withRef(movableGestureRef);
@@ -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) {
@@ -32,6 +32,7 @@
32
32
  * ✔ bindscroll
33
33
  */
34
34
  import { ScrollView, RefreshControl, Gesture, GestureDetector } from 'react-native-gesture-handler';
35
+ import { Animated as RNAnimated } from 'react-native';
35
36
  import { isValidElement, Children, useRef, useState, useEffect, forwardRef, useContext, useMemo, createElement } from 'react';
36
37
  import Animated, { useAnimatedRef, useSharedValue, withTiming, useAnimatedStyle, runOnJS } from 'react-native-reanimated';
37
38
  import { warn, hasOwn } from '@mpxjs/utils';
@@ -40,9 +41,11 @@ import useNodesRef from './useNodesRef';
40
41
  import { splitProps, splitStyle, useTransformStyle, useLayout, wrapChildren, extendObject, flatGesture, HIDDEN_STYLE } from './utils';
41
42
  import { IntersectionObserverContext, ScrollViewContext } from './context';
42
43
  import Portal from './mpx-portal';
44
+ const AnimatedScrollView = RNAnimated.createAnimatedComponent(ScrollView);
43
45
  const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
44
46
  const { textProps, innerProps: props = {} } = splitProps(scrollViewProps);
45
- const { enhanced = false, bounces = true, style = {}, binddragstart, binddragging, binddragend, bindtouchstart, bindtouchmove, bindtouchend, 'scroll-x': scrollX = false, 'scroll-y': scrollY = false, 'enable-back-to-top': enableBackToTop = false, 'enable-trigger-intersection-observer': enableTriggerIntersectionObserver = false, 'paging-enabled': pagingEnabled = false, 'upper-threshold': upperThreshold = 50, 'lower-threshold': lowerThreshold = 50, 'scroll-with-animation': scrollWithAnimation = false, 'refresher-enabled': refresherEnabled, 'refresher-default-style': refresherDefaultStyle, 'refresher-background': refresherBackground, 'refresher-threshold': refresherThreshold = 45, 'show-scrollbar': showScrollbar = true, 'scroll-into-view': scrollIntoView = '', 'scroll-top': scrollTop = 0, 'scroll-left': scrollLeft = 0, 'refresher-triggered': refresherTriggered, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'simultaneous-handlers': originSimultaneousHandlers, 'wait-for': waitFor, 'scroll-event-throttle': scrollEventThrottle = 0, __selectRef } = props;
47
+ const { enhanced = false, bounces = true, style = {}, binddragstart, binddragging, binddragend, bindtouchstart, bindtouchmove, bindtouchend, 'scroll-x': scrollX = false, 'scroll-y': scrollY = false, 'enable-back-to-top': enableBackToTop = false, 'enable-trigger-intersection-observer': enableTriggerIntersectionObserver = false, 'paging-enabled': pagingEnabled = false, 'upper-threshold': upperThreshold = 50, 'lower-threshold': lowerThreshold = 50, 'scroll-with-animation': scrollWithAnimation = false, 'refresher-enabled': refresherEnabled, 'refresher-default-style': refresherDefaultStyle, 'refresher-background': refresherBackground, 'refresher-threshold': refresherThreshold = 45, 'show-scrollbar': showScrollbar = true, 'scroll-into-view': scrollIntoView = '', 'scroll-top': scrollTop = 0, 'scroll-left': scrollLeft = 0, 'refresher-triggered': refresherTriggered, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'simultaneous-handlers': originSimultaneousHandlers, 'wait-for': waitFor, 'enable-sticky': enableSticky, 'scroll-event-throttle': scrollEventThrottle = 0, __selectRef } = props;
48
+ const scrollOffset = useRef(new RNAnimated.Value(0)).current;
46
49
  const simultaneousHandlers = flatGesture(originSimultaneousHandlers);
47
50
  const waitForHandlers = flatGesture(waitFor);
48
51
  const snapScrollTop = useRef(0);
@@ -90,12 +93,13 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
90
93
  },
91
94
  gestureRef: scrollViewRef
92
95
  });
96
+ const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef, onLayout });
93
97
  const contextValue = useMemo(() => {
94
98
  return {
95
- gestureRef: scrollViewRef
99
+ gestureRef: scrollViewRef,
100
+ scrollOffset
96
101
  };
97
102
  }, []);
98
- const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef, onLayout });
99
103
  const hasRefresherLayoutRef = useRef(false);
100
104
  // layout 完成前先隐藏,避免安卓闪烁问题
101
105
  const refresherLayoutStyle = useMemo(() => { return !hasRefresherLayoutRef.current ? HIDDEN_STYLE : {}; }, [hasRefresherLayoutRef.current]);
@@ -321,6 +325,12 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
321
325
  updateScrollOptions(e, { scrollLeft, scrollTop });
322
326
  updateIntersection();
323
327
  }
328
+ const scrollHandler = RNAnimated.event([{ nativeEvent: { contentOffset: { y: scrollOffset } } }], {
329
+ useNativeDriver: true,
330
+ listener: (event) => {
331
+ onScroll(event);
332
+ }
333
+ });
324
334
  function onScrollDragStart(e) {
325
335
  hasCallScrollToLower.current = false;
326
336
  hasCallScrollToUpper.current = false;
@@ -481,7 +491,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
481
491
  scrollEnabled: !enableScroll ? false : !!(scrollX || scrollY),
482
492
  bounces: false,
483
493
  ref: scrollViewRef,
484
- onScroll: onScroll,
494
+ onScroll: enableSticky ? scrollHandler : onScroll,
485
495
  onContentSizeChange: onContentSizeChange,
486
496
  bindtouchstart: ((enhanced && binddragstart) || bindtouchstart) && onScrollTouchStart,
487
497
  bindtouchmove: ((enhanced && binddragging) || bindtouchmove) && onScrollTouchMove,
@@ -523,13 +533,14 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
523
533
  'bindscrolltolower',
524
534
  'bindrefresherrefresh'
525
535
  ], { layoutRef });
526
- const withRefresherScrollView = createElement(GestureDetector, { gesture: panGesture }, createElement(ScrollView, innerProps, createElement(Animated.View, { style: [refresherAnimatedStyle, refresherLayoutStyle], onLayout: onRefresherLayout }, refresherContent), createElement(Animated.View, { style: contentAnimatedStyle }, createElement(ScrollViewContext.Provider, { value: contextValue }, wrapChildren(extendObject({}, props, { children: otherContent }), {
536
+ const ScrollViewComponent = enableSticky ? AnimatedScrollView : ScrollView;
537
+ const withRefresherScrollView = createElement(GestureDetector, { gesture: panGesture }, createElement(ScrollViewComponent, innerProps, createElement(Animated.View, { style: [refresherAnimatedStyle, refresherLayoutStyle], onLayout: onRefresherLayout }, refresherContent), createElement(Animated.View, { style: contentAnimatedStyle }, createElement(ScrollViewContext.Provider, { value: contextValue }, wrapChildren(extendObject({}, props, { children: otherContent }), {
527
538
  hasVarDec,
528
539
  varContext: varContextRef.current,
529
540
  textStyle,
530
541
  textProps
531
542
  })))));
532
- const commonScrollView = createElement(ScrollView, extendObject(innerProps, {
543
+ const commonScrollView = createElement(ScrollViewComponent, extendObject({}, innerProps, {
533
544
  refreshControl: refresherEnabled
534
545
  ? createElement(RefreshControl, extendObject({
535
546
  progressBackgroundColor: refresherBackground,
@@ -0,0 +1,115 @@
1
+ import { useEffect, useRef, useContext, forwardRef, useMemo, createElement, useId } from 'react';
2
+ import { Animated, StyleSheet, useAnimatedValue } from 'react-native';
3
+ import { ScrollViewContext, StickyContext } from './context';
4
+ import useNodesRef 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
+ const _StickyHeader = forwardRef((stickyHeaderProps = {}, ref) => {
9
+ const { textProps, innerProps: props = {} } = splitProps(stickyHeaderProps);
10
+ const { style, bindstickontopchange, padding = [0, 0, 0, 0], 'offset-top': offsetTop = 0, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
11
+ const scrollViewContext = useContext(ScrollViewContext);
12
+ const stickyContext = useContext(StickyContext);
13
+ const { scrollOffset } = scrollViewContext;
14
+ const { registerStickyHeader, unregisterStickyHeader } = stickyContext;
15
+ const headerRef = useRef(null);
16
+ const isStickOnTopRef = useRef(false);
17
+ const id = useId();
18
+ const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
19
+ const { layoutRef, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: headerRef, onLayout });
20
+ const { textStyle, innerStyle = {} } = splitStyle(normalStyle);
21
+ const headerTopAnimated = useAnimatedValue(0);
22
+ // harmony animatedValue 不支持通过 _value 访问
23
+ const headerTopRef = useRef(0);
24
+ useEffect(() => {
25
+ registerStickyHeader({ key: id, updatePosition });
26
+ return () => {
27
+ unregisterStickyHeader(id);
28
+ };
29
+ }, []);
30
+ function updatePosition() {
31
+ if (headerRef.current) {
32
+ const scrollViewRef = scrollViewContext.gestureRef;
33
+ if (scrollViewRef && scrollViewRef.current) {
34
+ headerRef.current.measureLayout(scrollViewRef.current, (left, top) => {
35
+ Animated.timing(headerTopAnimated, {
36
+ toValue: top,
37
+ duration: 0,
38
+ useNativeDriver: true
39
+ }).start();
40
+ headerTopRef.current = top;
41
+ });
42
+ }
43
+ else {
44
+ error('StickyHeader measureLayout error: scrollViewRef is not a valid native component reference');
45
+ }
46
+ }
47
+ }
48
+ function onLayout(e) {
49
+ updatePosition();
50
+ }
51
+ useNodesRef(props, ref, headerRef, {
52
+ style: normalStyle
53
+ });
54
+ useEffect(() => {
55
+ if (!bindstickontopchange)
56
+ return;
57
+ const listener = scrollOffset.addListener((state) => {
58
+ const currentScrollValue = state.value;
59
+ const newIsStickOnTop = currentScrollValue > headerTopRef.current;
60
+ if (newIsStickOnTop !== isStickOnTopRef.current) {
61
+ isStickOnTopRef.current = newIsStickOnTop;
62
+ bindstickontopchange(getCustomEvent('stickontopchange', {}, {
63
+ detail: {
64
+ isStickOnTop: newIsStickOnTop
65
+ },
66
+ layoutRef
67
+ }, props));
68
+ }
69
+ });
70
+ return () => {
71
+ scrollOffset.removeListener(listener);
72
+ };
73
+ }, []);
74
+ const animatedStyle = useMemo(() => {
75
+ const translateY = Animated.subtract(scrollOffset, headerTopAnimated).interpolate({
76
+ inputRange: [0, 1],
77
+ outputRange: [0, 1],
78
+ extrapolateLeft: 'clamp',
79
+ extrapolateRight: 'extend'
80
+ });
81
+ const finalTranslateY = offsetTop === 0
82
+ ? translateY
83
+ : Animated.add(translateY, Animated.subtract(scrollOffset, headerTopAnimated).interpolate({
84
+ inputRange: [0, 1],
85
+ outputRange: [0, offsetTop],
86
+ extrapolate: 'clamp'
87
+ }));
88
+ return {
89
+ transform: [{ translateY: finalTranslateY }]
90
+ };
91
+ }, [scrollOffset, headerTopAnimated, offsetTop]);
92
+ const innerProps = useInnerProps(extendObject({}, props, {
93
+ ref: headerRef,
94
+ style: extendObject({}, styles.content, innerStyle, animatedStyle, {
95
+ paddingTop: padding[0] || 0,
96
+ paddingRight: padding[1] || 0,
97
+ paddingBottom: padding[2] || 0,
98
+ paddingLeft: padding[3] || 0
99
+ })
100
+ }, layoutProps), [], { layoutRef });
101
+ return (createElement(Animated.View, innerProps, wrapChildren(props, {
102
+ hasVarDec,
103
+ varContext: varContextRef.current,
104
+ textStyle,
105
+ textProps
106
+ })));
107
+ });
108
+ const styles = StyleSheet.create({
109
+ content: {
110
+ width: '100%',
111
+ zIndex: 10
112
+ }
113
+ });
114
+ _StickyHeader.displayName = 'MpxStickyHeader';
115
+ export default _StickyHeader;
@@ -0,0 +1,45 @@
1
+ import { useRef, forwardRef, createElement, useCallback, useMemo } from 'react';
2
+ import { View } from 'react-native';
3
+ import useNodesRef from './useNodesRef';
4
+ import { splitProps, splitStyle, useTransformStyle, wrapChildren, useLayout, extendObject } from './utils';
5
+ import { StickyContext } from './context';
6
+ import useInnerProps from './getInnerListeners';
7
+ const _StickySection = forwardRef((stickySectionProps = {}, ref) => {
8
+ const { textProps, innerProps: props = {} } = splitProps(stickySectionProps);
9
+ const { style, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
10
+ const sectionRef = useRef(null);
11
+ const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
12
+ const { layoutRef, layoutProps, layoutStyle } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: sectionRef, onLayout });
13
+ const { textStyle, innerStyle = {} } = splitStyle(normalStyle);
14
+ const stickyHeaders = useRef(new Map());
15
+ const registerStickyHeader = useCallback((item) => {
16
+ stickyHeaders.current.set(item.id, item);
17
+ }, []);
18
+ const unregisterStickyHeader = useCallback((id) => {
19
+ stickyHeaders.current.delete(id);
20
+ }, []);
21
+ const contextValue = useMemo(() => ({
22
+ registerStickyHeader,
23
+ unregisterStickyHeader
24
+ }), []);
25
+ useNodesRef(props, ref, sectionRef, {
26
+ style: normalStyle
27
+ });
28
+ function onLayout() {
29
+ stickyHeaders.current.forEach(item => {
30
+ item.updatePosition();
31
+ });
32
+ }
33
+ const innerProps = useInnerProps(extendObject({}, props, {
34
+ style: extendObject(innerStyle, layoutStyle),
35
+ ref: sectionRef
36
+ }, layoutProps), [], { layoutRef });
37
+ return (createElement(View, innerProps, createElement(StickyContext.Provider, { value: contextValue }, wrapChildren(props, {
38
+ hasVarDec,
39
+ varContext: varContextRef.current,
40
+ textStyle,
41
+ textProps
42
+ }))));
43
+ });
44
+ _StickySection.displayName = 'MpxStickySection';
45
+ export default _StickySection;
@@ -2,7 +2,7 @@ import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated
2
2
  import { forwardRef, useRef, useContext, createElement } from 'react';
3
3
  import useInnerProps from './getInnerListeners';
4
4
  import useNodesRef from './useNodesRef'; // 引入辅助函数
5
- import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout, extendObject } from './utils';
5
+ import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout, extendObject, isHarmony } from './utils';
6
6
  import { SwiperContext } from './context';
7
7
  const _SwiperItem = forwardRef((props, ref) => {
8
8
  const { 'enable-var': enableVar, 'external-var-context': externalVarContext, style, customStyle, itemIndex } = props;
@@ -29,7 +29,7 @@ const _SwiperItem = forwardRef((props, ref) => {
29
29
  'style'
30
30
  ], { layoutRef });
31
31
  const itemAnimatedStyle = useAnimatedStyle(() => {
32
- if (!step.value)
32
+ if (!step.value && !isHarmony)
33
33
  return {};
34
34
  const inputRange = [step.value, 0];
35
35
  const outputRange = [0.7, 1];