diff --git a/packages/webpack-plugin/lib/platform/template/wx/component-config/picker-view.js b/packages/webpack-plugin/lib/platform/template/wx/component-config/picker-view.js index d18e4ae2d7..7ac0bce94c 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/component-config/picker-view.js +++ b/packages/webpack-plugin/lib/platform/template/wx/component-config/picker-view.js @@ -6,9 +6,7 @@ module.exports = function ({ print }) { const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false }) const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' }) const jdEventLog = print({ platform: 'jd', tag: TAG_NAME, isError: false, type: 'event' }) - const iosPropLog = print({ platform: 'ios', tag: TAG_NAME, isError: false }) const iosEventLog = print({ platform: 'ios', tag: TAG_NAME, isError: false, type: 'event' }) - const androidPropLog = print({ platform: 'android', tag: TAG_NAME, isError: false }) const androidEventLog = print({ platform: 'android', tag: TAG_NAME, isError: false, type: 'event' }) return { @@ -28,9 +26,7 @@ module.exports = function ({ print }) { props: [ { test: /^(indicator-class|mask-class)$/, - tt: ttPropLog, - ios: iosPropLog, - android: androidPropLog + tt: ttPropLog } ], event: [ diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx new file mode 100644 index 0000000000..7b242689d6 --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx @@ -0,0 +1,76 @@ +import React, { useEffect } from 'react' +import { LayoutChangeEvent } from 'react-native' +import Reanimated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated' +import { wrapChildren, extendObject } from './utils' +import { createFaces } from './pickerFaces' +import { usePickerViewColumnAnimationContext, usePickerViewStyleContext } from './pickerVIewContext' + +interface PickerColumnItemProps { + item: React.ReactElement + index: number + itemHeight: number + itemWidth?: number | '100%' + textStyle: Record + visibleCount: number + textProps?: any + onItemLayout?: (e: LayoutChangeEvent) => void +} + +const PickerViewColumnItem: React.FC = ({ + item, + index, + itemHeight, + itemWidth = '100%', + textStyle, + textProps, + visibleCount, + onItemLayout +}) => { + const textStyleFromAncestor = usePickerViewStyleContext() + const offsetYShared = usePickerViewColumnAnimationContext() + const facesShared = useSharedValue(createFaces(itemHeight, visibleCount)) + + useEffect(() => { + facesShared.value = createFaces(itemHeight, visibleCount) + }, [itemHeight]) + + const animatedStyles = useAnimatedStyle(() => { + const inputRange = facesShared.value.map((f) => itemHeight * (index + f.index)) + return { + opacity: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.opacity), Extrapolation.CLAMP), + transform: [ + { translateY: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.offsetY), Extrapolation.EXTEND) }, + { rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.CLAMP) + 'deg' }, + { scale: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.scale), Extrapolation.EXTEND) } + ] + } + }) + + const strKey = `picker-column-item-${index}` + const restProps = index === 0 ? { onLayout: onItemLayout } : {} + const itemProps = extendObject( + { + style: extendObject( + { height: itemHeight, width: '100%' }, + textStyleFromAncestor, + textStyle, + item.props.style + ) + }, + textProps, + restProps + ) + const realItem = React.cloneElement(item, itemProps) + + return ( + + {realItem} + + ) +} + +PickerViewColumnItem.displayName = 'MpxPickerViewColumnItem' +export default PickerViewColumnItem diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index a7ce4324d7..e465dd3655 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,17 +1,17 @@ - -import { View, Animated, SafeAreaView, NativeScrollEvent, NativeSyntheticEvent, LayoutChangeEvent, ScrollView } from 'react-native' -import React, { forwardRef, useRef, useState, useMemo, useCallback, useEffect } from 'react' -import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout, usePrevious } from './utils' +import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react' +import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, ScrollView, StyleSheet, View } from 'react-native' +import Reanimated, { AnimatedRef, useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated' +import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, isAndroid, isIOS } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' -import { createFaces } from './pickerFaces' -import PickerOverlay from './pickerOverlay' +import PickerIndicator from './pickerViewIndicator' +import PickerMask from './pickerViewMask' +import MpxPickerVIewColumnItem from './mpx-picker-view-column-item' +import { PickerViewColumnAnimationContext } from './pickerVIewContext' interface ColumnProps { children?: React.ReactNode columnData: React.ReactNode[] initialIndex: number - onColumnItemRawHChange: Function - getInnerLayout: Function onSelectChange: Function style: { [key: string]: any @@ -22,13 +22,11 @@ interface ColumnProps { height: number itemHeight: number } - pickerOverlayStyle: Record + pickerMaskStyle: Record + pickerIndicatorStyle: Record columnIndex: number } -// 默认的单个选项高度 -const DefaultPickerItemH = 36 -// 默认一屏可见选项个数 const visibleCount = 5 const _PickerViewColumn = forwardRef, ColumnProps>((props: ColumnProps, ref) => { @@ -37,57 +35,90 @@ const _PickerViewColumn = forwardRef, columnIndex, initialIndex, onSelectChange, - onColumnItemRawHChange, - getInnerLayout, style, wrapperStyle, - pickerOverlayStyle, + pickerMaskStyle, + pickerIndicatorStyle, 'enable-var': enableVar, 'external-var-context': externalVarContext } = props const { normalStyle, - hasVarDec, - varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext }) - const { textStyle } = splitStyle(normalStyle) - const { textProps } = splitProps(props) - const scrollViewRef = useRef(null) + const { textStyle = {} } = splitStyle(normalStyle) + const { textProps = {} } = splitProps(props) + const scrollViewRef = useAnimatedRef() + const offsetYShared = useScrollViewOffset(scrollViewRef as AnimatedRef) - useNodesRef(props, ref, scrollViewRef, { + useNodesRef(props, ref, scrollViewRef as AnimatedRef, { style: normalStyle }) - const { height: pickerH, itemHeight = DefaultPickerItemH } = wrapperStyle - const [itemRawH, setItemRawH] = useState(0) // 单个选项真实渲染高度 + const { height: pickerH, itemHeight } = wrapperStyle + const [itemRawH, setItemRawH] = useState(itemHeight) const maxIndex = useMemo(() => columnData.length - 1, [columnData]) + const prevScrollingInfo = useRef({ index: initialIndex, y: 0 }) const touching = useRef(false) const scrolling = useRef(false) + const timerResetPosition = useRef(null) + const timerScrollTo = useRef(null) const activeIndex = useRef(initialIndex) const prevIndex = usePrevious(initialIndex) const prevMaxIndex = usePrevious(maxIndex) - const initialOffset = useMemo(() => ({ - x: 0, - y: itemRawH * initialIndex - }), [itemRawH]) + const { + layoutProps + } = useLayout({ + props, + hasSelfPercent, + setWidth, + setHeight, + nodeRef: scrollViewRef + }) + + const paddingHeight = useMemo( + () => Math.round((pickerH - itemHeight) / 2), + [pickerH, itemHeight] + ) const snapToOffsets = useMemo( - () => columnData.map((_, i) => i * itemRawH), - [columnData, itemRawH] + () => Array.from({ length: maxIndex + 1 }, (_, i) => i * itemRawH), + [maxIndex, itemRawH] ) const contentContainerStyle = useMemo(() => { - return [ - { - paddingVertical: Math.round(pickerH - itemRawH) / 2 - } - ] - }, [pickerH, itemRawH]) + return [{ paddingVertical: paddingHeight }] + }, [paddingHeight]) + + const getIndex = useCallback((y: number) => { + const calc = Math.round(y / itemRawH) + return Math.max(0, Math.min(calc, maxIndex)) + }, [itemRawH, maxIndex]) + + const clearTimerResetPosition = useCallback(() => { + if (timerResetPosition.current) { + clearTimeout(timerResetPosition.current) + timerResetPosition.current = null + } + }, []) + + const clearTimerScrollTo = useCallback(() => { + if (timerScrollTo.current) { + clearTimeout(timerScrollTo.current) + timerScrollTo.current = null + } + }, []) + + useEffect(() => { + return () => { + clearTimerResetPosition() + clearTimerScrollTo() + } + }, []) useEffect(() => { if ( @@ -102,188 +133,180 @@ const _PickerViewColumn = forwardRef, ) { return } - + clearTimerScrollTo() + timerScrollTo.current = setTimeout(() => { + scrollViewRef.current?.scrollTo({ + x: 0, + y: initialIndex * itemRawH, + animated: false + }) + }, isAndroid ? 200 : 0) activeIndex.current = initialIndex - scrollViewRef.current.scrollTo({ - x: 0, - y: itemRawH * initialIndex, - animated: false - }) - }, [itemRawH, initialIndex]) - - const onScrollViewLayout = () => { - getInnerLayout && getInnerLayout(layoutRef) - } - - const { - layoutRef, - layoutProps - } = useLayout({ - props, - hasSelfPercent, - setWidth, - setHeight, - nodeRef: scrollViewRef, - onLayout: onScrollViewLayout - }) + }, [itemRawH, maxIndex, initialIndex]) - const onContentSizeChange = (w: number, h: number) => { - scrollViewRef.current?.scrollTo({ - x: 0, - y: itemRawH * initialIndex, - animated: false - }) - } + const onContentSizeChange = useCallback((_w: number, h: number) => { + const y = initialIndex * itemRawH + if (y <= h) { + clearTimerScrollTo() + timerScrollTo.current = setTimeout(() => { + scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) + }, 0) + } + }, [itemRawH, initialIndex]) - const onItemLayout = (e: LayoutChangeEvent) => { + const onItemLayout = useCallback((e: LayoutChangeEvent) => { const { height: rawH } = e.nativeEvent.layout - if (rawH && itemRawH !== rawH) { - setItemRawH(rawH) - onColumnItemRawHChange(rawH) + const roundedH = Math.round(rawH) + if (roundedH && roundedH !== itemRawH) { + setItemRawH(roundedH) } - } - - const onTouchStart = () => { - touching.current = true - } - - const onTouchEnd = () => { - touching.current = false - } + }, [itemRawH]) - const onTouchCancel = () => { - touching.current = false - } + const resetScrollPosition = useCallback((y: number) => { + if (touching.current || scrolling.current) { + return + } + scrolling.current = true + const targetIndex = getIndex(y) + scrollViewRef.current?.scrollTo({ x: 0, y: targetIndex * itemRawH, animated: false }) + }, [itemRawH, getIndex]) - const onMomentumScrollBegin = () => { + const onMomentumScrollBegin = useCallback(() => { + isIOS && clearTimerResetPosition() scrolling.current = true - } + }, []) - const onMomentumScrollEnd = (e: NativeSyntheticEvent) => { + const onMomentumScrollEnd = useCallback((e: NativeSyntheticEvent | { nativeEvent: { contentOffset: { y: number } } }) => { scrolling.current = false - if (!itemRawH) { - return - } const { y: scrollY } = e.nativeEvent.contentOffset - let calcIndex = Math.round(scrollY / itemRawH) - activeIndex.current = calcIndex - if (calcIndex !== initialIndex) { - calcIndex = Math.max(0, Math.min(calcIndex, maxIndex)) || 0 + if (isIOS && scrollY % itemRawH !== 0) { + return resetScrollPosition(scrollY) + } + const calcIndex = getIndex(scrollY) + if (calcIndex !== activeIndex.current) { + activeIndex.current = calcIndex onSelectChange(calcIndex) } - } - - const offsetY = useRef(new Animated.Value(0)).current + }, [itemRawH, getIndex, onSelectChange, resetScrollPosition]) - const onScroll = useMemo( - () => - Animated.event([{ nativeEvent: { contentOffset: { y: offsetY } } }], { - useNativeDriver: true - }), - [offsetY] - ) + const onScrollBeginDrag = useCallback(() => { + isIOS && clearTimerResetPosition() + touching.current = true + prevScrollingInfo.current = { + index: activeIndex.current, + y: activeIndex.current * itemRawH + } + }, [itemRawH]) - const faces = useMemo(() => createFaces(itemRawH, visibleCount), [itemRawH]) + const onScrollEndDrag = useCallback((e: NativeSyntheticEvent) => { + touching.current = false + if (isIOS) { + const { y } = e.nativeEvent.contentOffset + if (y % itemRawH === 0) { + onMomentumScrollEnd({ nativeEvent: { contentOffset: { y } } }) + } else if (y > 0 && y < snapToOffsets[maxIndex]) { + timerResetPosition.current = setTimeout(() => { + resetScrollPosition(y) + }, 10) + } + } + }, [itemRawH, maxIndex, snapToOffsets, onMomentumScrollEnd, resetScrollPosition]) - const getTransform = useCallback( - (index: number) => { - const inputRange = faces.map((f) => itemRawH * (index + f.index)) - return { - opacity: offsetY.interpolate({ - inputRange: inputRange, - outputRange: faces.map((x) => x.opacity), - extrapolate: 'clamp' - }), - rotateX: offsetY.interpolate({ - inputRange: inputRange, - outputRange: faces.map((x) => `${x.deg}deg`), - extrapolate: 'extend' - }), - translateY: offsetY.interpolate({ - inputRange: inputRange, - outputRange: faces.map((x) => x.offsetY), - extrapolate: 'extend' - }) + const onScroll = useCallback((e: NativeSyntheticEvent) => { + // 全局注册的振动触感 hook + const pickerVibrate = global.__mpx?.config?.rnConfig?.pickerVibrate + if (typeof pickerVibrate !== 'function') { + return + } + const { y } = e.nativeEvent.contentOffset + const { index: prevIndex, y: _y } = prevScrollingInfo.current + if (touching.current || scrolling.current) { + if (Math.abs(y - _y) >= itemRawH) { + const currentId = getIndex(y) + if (currentId !== prevIndex) { + prevScrollingInfo.current = { + index: currentId, + y: currentId * itemRawH + } + // vibrateShort({ type: 'selection' }) + pickerVibrate() + } } - }, - [offsetY, faces, itemRawH] - ) + } + }, [itemRawH, getIndex]) const renderInnerchild = () => - columnData.map((item: React.ReactNode, index: number) => { - const InnerProps = index === 0 ? { onLayout: onItemLayout } : {} - const strKey = `picker-column-${columnIndex}-${index}` - const { opacity, rotateX, translateY } = getTransform(index) + columnData.map((item: React.ReactElement, index: number) => { return ( - - {wrapChildren( - { children: item }, - { - hasVarDec, - varContext: varContextRef.current, - textStyle, - textProps - } - )} - + ) }) const renderScollView = () => { return ( - - {renderInnerchild()} - + + + {renderInnerchild()} + + ) } - const renderOverlay = () => ( - + const renderIndicator = () => ( + + ) + + const renderMask = () => ( + ) return ( - + {renderScollView()} - {renderOverlay()} - + {renderMask()} + {renderIndicator()} + ) }) +const styles = StyleSheet.create({ + wrapper: { display: 'flex', flex: 1 } +}) + _PickerViewColumn.displayName = 'MpxPickerViewColumn' export default _PickerViewColumn diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx index 282bf1a394..3ef2249c6a 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx @@ -1,5 +1,5 @@ import { View } from 'react-native' -import React, { forwardRef, useState, useRef } from 'react' +import React, { forwardRef, useRef } from 'react' import useInnerProps, { getCustomEvent } from './getInnerListeners' import useNodesRef, { HandlerRef } from './useNodesRef' import { @@ -7,35 +7,33 @@ import { splitProps, splitStyle, wrapChildren, - parseInlineStyle, useTransformStyle, - useDebounceCallback, - useStableCallback, extendObject } from './utils' +import { PickerViewStyleContext } from './pickerVIewContext' import type { AnyFunc } from './types/common' /** * ✔ value * ✔ bindchange * ✘ bindpickstart * ✘ bindpickend - * ✘ mask-class + * ✔ mask-class * ✔ indicator-style: 优先级indicator-style.height > pick-view-column中的子元素设置的height - * ✘ indicator-class - * ✘ mask-style + * WebView Only: + * ✔ indicator-class + * ✔ mask-style * ✘ immediate-change */ interface PickerViewProps { children: React.ReactNode - // 初始的defaultValue数组中的数字依次表示 picker-view 内的 picker-view-column 选择的第几项(下标从 0 开始), - // 数字大于 picker-view-column 可选项长度时,选择最后一项。 value?: Array bindchange?: AnyFunc style: { [key: string]: any } - 'indicator-style'?: string + 'indicator-style'?: Record, + 'mask-style'?: Record, 'enable-var': boolean 'external-var-context'?: Record, 'enable-offset': boolean @@ -62,25 +60,25 @@ const styles: { [key: string]: Object } = { } } +const DefaultPickerItemH = 36 + const _PickerView = forwardRef, PickerViewProps>((props: PickerViewProps, ref) => { const { children, value = [], bindchange, style, + 'indicator-style': indicatorStyle = {}, + 'mask-style': pickerMaskStyle = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext } = props - - // indicatorStyle 需要转换为rn的style - // 微信设置到pick-view上上设置的normalStyle如border等需要转换成RN的style然后进行透传 - const indicatorStyle = parseInlineStyle(props['indicator-style']) - const { height: indicatorH, ...pickerOverlayStyle } = indicatorStyle - const [pickMaxH, setPickMaxH] = useState(0) + const { height: indicatorH, ...pickerIndicatorStyle } = indicatorStyle const nodeRef = useRef(null) const cloneRef = useRef(null) const activeValueRef = useRef(value) activeValueRef.current = value.slice() + const snapActiveValueRef = useRef(null) const { normalStyle, @@ -96,7 +94,6 @@ const _PickerView = forwardRef, PickerViewProp }) const { - // 存储layout布局信息 layoutRef, layoutProps, layoutStyle @@ -104,33 +101,32 @@ const _PickerView = forwardRef, PickerViewProp const { textProps } = splitProps(props) const { textStyle } = splitStyle(normalStyle) - const onColumnItemRawHChange = (height: number) => { - if (height > pickMaxH) { - setPickMaxH(height) - } - } - - const bindchangeDebounce = useDebounceCallback(useStableCallback(bindchange), 300) - const onSelectChange = (columnIndex: number, selectedIndex: number) => { - bindchangeDebounce.clear() const activeValue = activeValueRef.current activeValue[columnIndex] = selectedIndex const eventData = getCustomEvent( 'change', {}, - { detail: { value: activeValue, source: 'change' }, layoutRef } + { detail: { value: activeValue.slice(), source: 'change' }, layoutRef } ) - bindchangeDebounce(eventData) + bindchange?.(eventData) + snapActiveValueRef.current = activeValueRef.current } - const onInitialChange = (value: number[]) => { - const eventData = getCustomEvent( - 'change', - {}, - { detail: { value, source: 'change' }, layoutRef } - ) - bindchange?.(eventData) // immediate + const hasDiff = (a: number[] = [], b: number[]) => { + return a.some((v, i) => v !== b[i]) + } + + const onInitialChange = (isInvalid: boolean, value: number[]) => { + if (isInvalid || !snapActiveValueRef.current || hasDiff(snapActiveValueRef.current, value)) { + const eventData = getCustomEvent( + 'change', + {}, + { detail: { value: value.slice(), source: 'change' }, layoutRef } + ) + bindchange?.(eventData) + snapActiveValueRef.current = value.slice() + } } const innerProps = useInnerProps( @@ -148,12 +144,17 @@ const _PickerView = forwardRef, PickerViewProp ), layoutProps }), - ['enable-offset'], + [ + 'enable-offset', + 'indicator-style', + 'indicator-class', + 'mask-style', + 'mask-class' + ], { layoutRef } ) const renderColumn = (child: React.ReactElement, index: number, columnData: React.ReactNode[], initialIndex: number) => { - const extraProps = {} const childProps = child?.props || {} const wrappedProps = extendObject( {}, @@ -164,15 +165,14 @@ const _PickerView = forwardRef, PickerViewProp columnIndex: index, key: `pick-view-${index}`, wrapperStyle: { - height: normalStyle?.height || 0, - itemHeight: indicatorH || 0 + height: normalStyle?.height || DefaultPickerItemH, + itemHeight: indicatorH || DefaultPickerItemH }, - onColumnItemRawHChange, onSelectChange: onSelectChange.bind(null, index), initialIndex, - pickerOverlayStyle - }, - extraProps + pickerIndicatorStyle, + pickerMaskStyle + } ) const realElement = React.cloneElement(child, wrappedProps) return wrapChildren( @@ -215,17 +215,18 @@ const _PickerView = forwardRef, PickerViewProp validValue.push(validIndex) renderColumns.push(renderColumn(item, index, columnData, validIndex)) }) - isInvalid && onInitialChange(validValue) + onInitialChange(isInvalid, validValue) return renderColumns } return ( - - {renderPickerColumns()} - + + + {renderPickerColumns()} + + ) }) _PickerView.displayName = 'MpxPickerView' - export default _PickerView diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts b/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts index f25dc0313f..329d6adbcd 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts @@ -8,6 +8,7 @@ export type Faces = { deg: number offsetY: number opacity: number + scale: number screenHeight: number } @@ -33,7 +34,7 @@ export const createFaces = ( const maxStep = Math.trunc((visibleCount + 2) / 2) // + 2 because there are 2 more faces at 90 degrees const stepDegree = 90 / maxStep - const result = [] + const result: number[] = [] for (let i = 1; i <= maxStep; i++) { result.push(i * stepDegree) } @@ -62,17 +63,17 @@ export const createFaces = ( const getOpacity = (index: number) => { const map: Record = { 0: 0, - 1: 0.2, - 2: 0.35, - 3: 0.45, - 4: 0.5 + 1: 0.8, + 2: 0.9 } - return map[index] ?? Math.min(1, map[4] + index * 0.5) + return map[index] ?? Math.min(1, map[2] + index * 0.05) } const degrees = getDegreesRelativeCenter() const [screenHeight, offsets] = getScreenHeightsAndOffsets(degrees) + const scales = [0.973, 0.9, 0.8] + return [ // top items ...degrees @@ -82,13 +83,14 @@ export const createFaces = ( deg: degree, opacity: getOpacity(degrees.length - 1 - index), offsetY: -1 * offsets[index], + scale: scales[index], screenHeight: screenHeight[index] } }) .reverse(), // center item - { index: 0, deg: 0, opacity: 1, offsetY: 0, screenHeight: itemHeight }, + { index: 0, deg: 0, opacity: 1, offsetY: 0, scale: 1, screenHeight: itemHeight }, // bottom items ...degrees.map((degree, index) => { @@ -97,6 +99,7 @@ export const createFaces = ( deg: -1 * degree, opacity: getOpacity(degrees.length - 1 - index), offsetY: offsets[index], + scale: scales[index], screenHeight: screenHeight[index] } }) diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerOverlay.tsx b/packages/webpack-plugin/lib/runtime/components/react/pickerOverlay.tsx deleted file mode 100644 index 4a11903d6e..0000000000 --- a/packages/webpack-plugin/lib/runtime/components/react/pickerOverlay.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' - -type OverlayProps = { - itemHeight: number - overlayItemStyle?: StyleProp - overlayContainerStyle?: StyleProp -} - -const Overlay = ({ itemHeight, overlayItemStyle, overlayContainerStyle }: OverlayProps) => { - return ( - - - - ) -} - -const styles = StyleSheet.create({ - overlayContainer: { - ...StyleSheet.absoluteFillObject, - justifyContent: 'center', - alignItems: 'center' - }, - selection: { - borderTopWidth: 1, - borderBottomWidth: 1, - borderColor: 'rgba(0, 0, 0, 0.05)', - alignSelf: 'stretch' - } -}) - -export default React.memo(Overlay) diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerVIewContext.ts b/packages/webpack-plugin/lib/runtime/components/react/pickerVIewContext.ts new file mode 100644 index 0000000000..8ab8e588e9 --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerVIewContext.ts @@ -0,0 +1,27 @@ +import { createContext, useContext } from 'react' +import { SharedValue } from 'react-native-reanimated' + +type ContextValue = SharedValue + +export const PickerViewColumnAnimationContext = createContext< + ContextValue | undefined +>(undefined) + +export const usePickerViewColumnAnimationContext = () => { + const value = useContext(PickerViewColumnAnimationContext) + if (value === undefined) { + throw new Error( + 'usePickerViewColumnAnimationContext must be called from within PickerViewColumnAnimationContext.Provider!' + ) + } + return value +} + +export const PickerViewStyleContext = createContext< + Record | undefined +>(undefined) + +export const usePickerViewStyleContext = () => { + const value = useContext(PickerViewStyleContext) + return value +} diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerViewIndicator.tsx b/packages/webpack-plugin/lib/runtime/components/react/pickerViewIndicator.tsx new file mode 100644 index 0000000000..3c745b059d --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerViewIndicator.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' + +type IndicatorProps = { + itemHeight: number + indicatorItemStyle?: StyleProp + indicatorContainerStyle?: StyleProp +} + +const _PickerViewIndicator = ({ itemHeight, indicatorItemStyle, indicatorContainerStyle }: IndicatorProps) => { + return ( + + + + ) +} + +const styles = StyleSheet.create({ + indicatorContainer: { + ...StyleSheet.absoluteFillObject, + justifyContent: 'center', + alignItems: 'center', + zIndex: 200 + }, + selection: { + borderTopWidth: 1, + borderBottomWidth: 1, + borderColor: 'rgba(0, 0, 0, 0.05)', + alignSelf: 'stretch' + } +}) + +_PickerViewIndicator.displayName = 'MpxPickerViewIndicator' +export default _PickerViewIndicator diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx b/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx new file mode 100644 index 0000000000..a0858122ba --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' +import LinearGradient from 'react-native-linear-gradient' + +type MaskProps = { + itemHeight: number + maskContainerStyle?: StyleProp +} + +const _PickerViewMask = ({ + itemHeight, + maskContainerStyle +}: MaskProps) => { + return ( + + + + + + ) +} +const styles = StyleSheet.create({ + maskContainer: { + ...StyleSheet.absoluteFillObject, + zIndex: 100 + } +}) + +_PickerViewMask.displayName = 'MpxPickerViewMask' +export default _PickerViewMask diff --git a/packages/webpack-plugin/lib/runtime/components/react/utils.tsx b/packages/webpack-plugin/lib/runtime/components/react/utils.tsx index f14a5e6ad8..6a9b685adb 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/utils.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/utils.tsx @@ -1,5 +1,5 @@ import { useEffect, useCallback, useMemo, useRef, ReactNode, ReactElement, isValidElement, useContext, useState, Dispatch, SetStateAction, Children, cloneElement } from 'react' -import { LayoutChangeEvent, TextStyle, ImageProps, Image } from 'react-native' +import { LayoutChangeEvent, TextStyle, ImageProps, Image, Platform } from 'react-native' import { isObject, isFunction, isNumber, hasOwn, diffAndCloneA, error, warn } from '@mpxjs/utils' import { VarContext, ScrollViewContext } from './context' import { ExpressionParser, parseFunc, ReplaceSource } from './parser' @@ -21,6 +21,11 @@ export const HIDDEN_STYLE = { opacity: 0 } +declare const __mpx_mode__: 'ios' | 'android' + +export const isIOS = __mpx_mode__ === 'ios' +export const isAndroid = __mpx_mode__ === 'android' + const varDecRegExp = /^--/ const varUseRegExp = /var\(/ const unoVarDecRegExp = /^--un-/ @@ -568,13 +573,14 @@ export const debounce = ( ): ((...args: Parameters) => void) & { clear: () => void } => { let timer: any const wrapper = (...args: ReadonlyArray) => { - clearTimeout(timer) + timer && clearTimeout(timer) timer = setTimeout(() => { func(...args) }, delay) } wrapper.clear = () => { - clearTimeout(timer) + timer && clearTimeout(timer) + timer = null } return wrapper } diff --git a/packages/webpack-plugin/lib/template-compiler/compiler.js b/packages/webpack-plugin/lib/template-compiler/compiler.js index 2c565454b5..9445aad991 100644 --- a/packages/webpack-plugin/lib/template-compiler/compiler.js +++ b/packages/webpack-plugin/lib/template-compiler/compiler.js @@ -38,7 +38,7 @@ const endTag = new RegExp(('^<\\/' + qnameCapture + '[^>]*>')) const doctype = /^]+>/i const comment = /^