UNPKG

@mpxjs/webpack-plugin

Version:

mpx compile core

118 lines (117 loc) 5.16 kB
import { useEffect, useRef, useContext, forwardRef, useMemo, createElement, useId } from 'react'; import { Animated, StyleSheet, useAnimatedValue } from 'react-native'; import { ScrollViewContext, StickyContext } from './context'; import useNodesRef from './useNodesRef'; import { splitProps, splitStyle, useTransformStyle, wrapChildren, useLayout, extendObject } from './utils'; import { error } from '@mpxjs/utils'; import useInnerProps, { getCustomEvent } from './getInnerListeners'; const _StickyHeader = forwardRef((stickyHeaderProps = {}, ref) => { const { textProps, innerProps: props = {} } = splitProps(stickyHeaderProps); 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; const scrollViewContext = useContext(ScrollViewContext); const stickyContext = useContext(StickyContext); const { scrollOffset } = scrollViewContext; const { registerStickyHeader, unregisterStickyHeader } = stickyContext; const headerRef = useRef(null); const isStickOnTopRef = useRef(false); const id = useId(); const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight }); const { layoutRef, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: headerRef, onLayout }); const { textStyle, innerStyle = {} } = splitStyle(normalStyle); const headerTopAnimated = useAnimatedValue(0); // harmony animatedValue 不支持通过 _value 访问 const headerTopRef = useRef(0); useEffect(() => { registerStickyHeader({ key: id, updatePosition }); return () => { unregisterStickyHeader(id); }; }, []); function updatePosition() { if (headerRef.current) { const scrollViewRef = scrollViewContext.gestureRef; if (scrollViewRef && scrollViewRef.current) { headerRef.current.measureLayout(scrollViewRef.current, (left, top) => { Animated.timing(headerTopAnimated, { toValue: top, duration: 0, useNativeDriver: true }).start(); headerTopRef.current = top; }); } else { error('StickyHeader measureLayout error: scrollViewRef is not a valid native component reference'); } } } function onLayout(e) { updatePosition(); } useNodesRef(props, ref, headerRef, { style: normalStyle }); useEffect(() => { if (!bindstickontopchange) return; const listener = scrollOffset.addListener((state) => { const currentScrollValue = state.value; const newIsStickOnTop = currentScrollValue > headerTopRef.current; if (newIsStickOnTop !== isStickOnTopRef.current) { isStickOnTopRef.current = newIsStickOnTop; bindstickontopchange(getCustomEvent('stickontopchange', {}, { detail: { isStickOnTop: newIsStickOnTop }, layoutRef }, props)); } }); return () => { scrollOffset.removeListener(listener); }; }, []); const animatedStyle = useMemo(() => { const translateY = Animated.subtract(scrollOffset, headerTopAnimated).interpolate({ inputRange: [0, 1], outputRange: [0, 1], extrapolateLeft: 'clamp', extrapolateRight: 'extend' }); const finalTranslateY = offsetTop === 0 ? translateY : Animated.add(translateY, Animated.subtract(scrollOffset, headerTopAnimated).interpolate({ inputRange: [0, 1], outputRange: [0, offsetTop], extrapolate: 'clamp' })); return { transform: [{ translateY: finalTranslateY }] }; }, [scrollOffset, headerTopAnimated, offsetTop]); const innerProps = useInnerProps(extendObject({}, props, { ref: headerRef, style: extendObject({}, styles.content, innerStyle, animatedStyle, { paddingTop: padding[0] || 0, paddingRight: padding[1] || 0, paddingBottom: padding[2] || 0, paddingLeft: padding[3] || 0 }) }, layoutProps), [], { layoutRef }); return (createElement(Animated.View, innerProps, wrapChildren(props, { hasVarDec, varContext: varContextRef.current, textStyle, textProps }))); }); const styles = StyleSheet.create({ content: { width: '100%', zIndex: 10, // harmony 需要手动设置 relative, zIndex 才生效 position: 'relative' } }); _StickyHeader.displayName = 'MpxStickyHeader'; export default _StickyHeader;