diff --git a/package.json b/package.json index 41d831e2c9..cad1d9f705 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "@docsearch/js": "^3.0.0", "@testing-library/jest-dom": "^4.2.4", "@types/jest": "^27.0.1", + "@typescript-eslint/eslint-plugin": "^5.2.0", + "@typescript-eslint/parser": "^5.2.0", "@vuepress/plugin-back-to-top": "^1.8.2", "@vuepress/plugin-pwa": "^1.8.0", "eslint": "^7.32.0", @@ -30,8 +32,6 @@ "eslint-plugin-jest": "^27.0.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.1", - "@typescript-eslint/eslint-plugin": "^5.2.0", - "@typescript-eslint/parser": "^5.2.0", "identity-obj-proxy": "^3.0.0", "jest": "^27.2.0", "lerna": "^8.1.8", @@ -40,5 +40,9 @@ }, "workspaces": [ "packages/*" - ] + ], + "dependencies": { + "@types/react": "^18.3.11", + "react-native-linear-gradient": "^2.8.3" + } } 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 01c7be4e70..b641d76745 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,27 +1,128 @@ -import { View } from 'react-native' -import React, { forwardRef, useRef } from 'react' -import useInnerProps from './getInnerListeners' +import { View, Animated, SafeAreaView, NativeScrollEvent, NativeSyntheticEvent, LayoutChangeEvent, ScrollView } from 'react-native' +import React, { forwardRef, useRef, useState, useEffect, ReactElement } from 'react' +// import { Reanimated } from 'react-native-reanimated'; import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数 - interface ColumnProps { - children: React.ReactNode + children: React.ReactNode, + selectedIndex: number, + onColumnLayoutChange: Function, + getInnerLayout: Function, + onSelectChange: Function, + style: Object, + wrapperStyle: { + height?: number + }, + prefix: number } - -const _PickerViewColumn = forwardRef, ColumnProps>((props: ColumnProps, ref) => { - const { children } = props +// 每个Column 都有个外层的高度, 内部的元素高度 +// 默认的高度 +// const AnimatedScrollView = Reanimated.createAnimatedComponent(ScrollView); +const _PickerViewColumn = forwardRef, ColumnProps>((props: ColumnProps, ref) => { + const { children, selectedIndex, onColumnLayoutChange, onSelectChange, getInnerLayout, wrapperStyle } = props + // scrollView的ref + const { nodeRef: scrollViewRef } = useNodesRef(props, ref, {}) + // scrollView的布局存储 const layoutRef = useRef({}) - const { nodeRef } = useNodesRef(props, ref, {}) - const innerProps = useInnerProps(props, {}, [], { layoutRef }) - return ( - - {children} - - ) + // 每个元素的高度 + let [itemH, setItemH] = useState(0) + // scrollView内容的初始offset + let [offset, setOffset] = useState(0) + + useEffect(() => { + if (selectedIndex && itemH) { + offset = (selectedIndex + 2) * itemH + setOffset(offset) + } + }, [selectedIndex, itemH]) + + const onScrollViewLayout = () => { + scrollViewRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => { + layoutRef.current = { x, y, width, height, offsetLeft, offsetTop } + getInnerLayout && getInnerLayout(layoutRef) + }) + } + + const onItemLayout = (e: LayoutChangeEvent) => { + const layout = e.nativeEvent.layout + if (layout.height && itemH !== layout.height) { + itemH = layout.height + setItemH(layout.height) + onColumnLayoutChange && onColumnLayoutChange({ height: layout.height * 5 }) + } + } + + const onMomentumScrollEnd = (e: NativeSyntheticEvent) => { + if (scrollViewRef && itemH) { + const { y: scrollY } = e.nativeEvent.contentOffset + const selIndex = scrollY / itemH + onSelectChange(selIndex) + } + } + + const renderInnerchild = () => { + // Fragment 节点 + const getRealChilds = () => { + if (Array.isArray(children)) { + return children + } else { + const tempChild = children as ReactElement + if (tempChild.props.children && tempChild.props.children) { + return tempChild.props.children + } else { + return [children] + } + } + } + // const realChilds = Array.isArray(children) ? children : (children?.props?.children && Array.isArray(children.props?.children) ? children.props.children : [children]) + const realChilds = getRealChilds() + const arrChild = realChilds.map((item: React.ReactNode, index: number) => { + const InnerProps = index === 0 ? { onLayout: onItemLayout } : {} + const strKey = 'picker' + props.prefix + '-column' + index + return {item} + }) + const totalHeight = itemH * 5 + if (wrapperStyle.height && totalHeight > wrapperStyle.height) { + const fix = Math.ceil((totalHeight - wrapperStyle.height) / 2) + arrChild.unshift() + arrChild.unshift() + arrChild.push() + arrChild.push() + } else { + arrChild.unshift() + arrChild.unshift() + arrChild.push() + arrChild.push() + } + return arrChild + } + + const renderScollView = () => { + const contentContainerStyle = { + textAlign: 'center' + } + + return ( + {renderInnerchild()} + ) + } + + return ( + { renderScollView() } + ) }) _PickerViewColumn.displayName = 'mpx-picker-view-column' - 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 074226df30..1115248aae 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,6 +1,6 @@ import { View } from 'react-native' -import React, { forwardRef, useState, useRef, useEffect } from 'react' -import { PickerView } from '@ant-design/react-native' +import { LinearGradient, LinearGradientProps } from 'react-native-linear-gradient' +import React, { forwardRef, MutableRefObject, useState, useRef, ReactElement, JSX } from 'react' import useInnerProps, { getCustomEvent } from './getInnerListeners' import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数 /** @@ -20,81 +20,146 @@ interface PickerViewProps { // 初始的defaultValue数组中的数字依次表示 picker-view 内的 picker-view-column 选择的第几项(下标从 0 开始),数字大于 picker-view-column 可选项长度时,选择最后一项。 value?: Array bindchange?: Function + style?: { + height?: number + } } -const _PickerView = forwardRef, PickerViewProps>((props: PickerViewProps, ref) => { - const { children, ...restProps } = props - const layoutRef = useRef({}) - const { nodeRef } = useNodesRef(props, ref, {}) - const [value, setValue] = useState(props.value) - useEffect(() => { - // 确认这个是变化的props变化的时候才执行,还是初始化的时候就执行 - setValue(props.value) - }, [props.value]) +interface PickerLayout { + height: number, + itemHeight: number +} - const onLayout = () => { - nodeRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => { - layoutRef.current = { x, y, width, height, offsetLeft, offsetTop } - }) +interface PosType { + height?: number, + top?: number +} + +const styles: { [key: string]: Object } = { + wrapper: { + display: 'flex', + flex: 1, + flexDirection: 'row', + justifyContent: 'space-around', + overflow: 'hidden', + alignItems: 'center' + }, + maskTop: { + position: 'absolute', + width: 1000, + zIndex: 100 + }, + maskBottom: { + position: 'absolute', + width: 1000, + zIndex: 100 } - const innerProps = useInnerProps(props, {}, [], { layoutRef }) +} +const _PickerView = forwardRef, PickerViewProps>((props: PickerViewProps, ref) => { + const { children, value = [], bindchange, style } = props + const innerLayout = useRef({}) + const cloneRef = useRef(null) + const wrapRef = useRef(null) + const maskPos: PosType = {} + let [pickH, setPickH] = useState(0) + if (style?.height && pickH) { + maskPos.height = pickH / 5 * 2 + (style.height - pickH) + } else { + maskPos.height = pickH / 5 * 2 + } + + const { nodeRef } = useNodesRef(props, ref, {}) + + // value 如何关联picker-view-column这几个slot的内容呢 - const onChange = (val: Array): void => { - const eventData = getCustomEvent('change', {}, { detail: { value: val, source: 'touch' }, layoutRef: layoutRef }) - setValue(val) - props.bindchange && props.bindchange(eventData) + const onColumnLayoutChange = (layoutConfig: PickerLayout) => { + pickH = layoutConfig.height + setPickH(layoutConfig.height) } - const joinString = (data: string | any[] | React.ReactElement): string => { - return (Array.isArray(data) ? data : [data]).join('') + const onSelectChange = (columnIndex: number, selIndex: number) => { + const changeValue = value.slice() + changeValue[columnIndex] = selIndex + const eventData = getCustomEvent('change', {}, { detail: { value: changeValue, source: 'change' }, layoutRef: innerLayout }) + bindchange && bindchange(eventData) + } + /* + const onWrapperLayout = () => { + wrapRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => { + const a = { x, y, width, height, offsetLeft, offsetTop } + }) + } + */ + const getInnerLayout = (layout: MutableRefObject<{}>) => { + innerLayout.current = layout.current } - const getLabelFromChildren = (child: React.ReactElement): string => { - return child.props && child.props.children ? getLabelFromChildren(child.props.children) : joinString(child) + const innerProps = useInnerProps(props, { ref: nodeRef }, [], { layoutRef: innerLayout }) + + const cloneChild = (child: React.ReactNode, index: number) => { + const extraProps = index === 0 ? { getInnerLayout: getInnerLayout, innerProps } : {} + const childProps = { + ...(child as ReactElement)?.props, + ref: cloneRef, + prefix: index, + key: 'pick-view' + index, + wrapperStyle: { + height: style?.height || 0 + }, + onColumnLayoutChange, + onSelectChange: onSelectChange.bind(null, index), + selectedIndex: value?.[index] || 0, + ...extraProps + } + return React.cloneElement(child as ReactElement, childProps) } - const handleChildren = (children: React.ReactNode[]): any[] => { - return children.map((child: any, index: number) => { - return { - label: getLabelFromChildren(child), - value: index - } - }) + const renderTopMask = () => { + const linearProps: LinearGradientProps = { + colors: ['rgba(255,255,255,0.8)', 'rgba(255,255,255,0.2)'], + style: [ + styles.maskTop, + { + height: maskPos.height, + top: 0, + pointerEvents: 'none' + } + ] + } + return () } - const getDataFromChildren = (children: React.ReactNode): any[] => { - return (Array.isArray(children) ? children : [children]).map((child: any) => { - return handleChildren(child.props && child.props.children ? child.props.children : [child]) - }) + const renderBottomMask = () => { + const linearProps: LinearGradientProps = { + colors: ['rgba(255,255,255,0.2)', 'rgba(255,255,255,0.8)'], + style: [ + styles.maskBottom, + { + height: maskPos.height, + bottom: 0, + pointerEvents: 'none' + } + ] + } + return } - const columns = Array.isArray(children) ? children.length : 1 - const originData = getDataFromChildren(children) - // 子节点默认的序号,这里是更新默认值的 - const subChildLength = originData.map((item) => { - return item.length - }) - const defaultValue = (props.value || []).map((item, index) => { - if (item > subChildLength[index]) { - return subChildLength[index] - 1 + const renderSubChild = () => { + if (Array.isArray(children)) { + return children.map((item, index) => { + return cloneChild(item, index) + }) } else { - return item + return cloneChild(children, 0) } - }) - - return ( - - ) + } + return ( + {renderTopMask()} + + {renderSubChild()} + + {renderBottomMask()} + ) }) _PickerView.displayName = 'mpx-picker-view' diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-swiper/carouse.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-swiper/carouse.tsx index a3e00bba8c..0111a94c11 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-swiper/carouse.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-swiper/carouse.tsx @@ -116,27 +116,35 @@ const _Carouse = forwardRef, Carouse */ function updateIndex (scrollViewOffset: NativeScrollPoint, useIndex = false) { const { nextIndex, nextOffset } = getNextConfig(scrollViewOffset) - internalsRef.current.offset = nextOffset + updateState(nextIndex, nextOffset) + // 更新完状态之后, 开启新的loop + } + + /** + * 更新索引状态 + */ + function updateState (index: number, offset: { x: number, y: number}) { + internalsRef.current.offset = offset setState((preState) => { const newState = { ...preState, - index: nextIndex, + index: index, // offset用来指示当前scrollView的偏移量 - offset: nextOffset + offset: offset } return newState }) internalsRef.current.isScrolling = false // getCustomEvent - const eventData = getCustomEvent('change', {}, { detail: { current: nextIndex, source: 'touch' }, layoutRef: layoutRef }) + const eventData = getCustomEvent('change', {}, { detail: { current: index, source: 'touch' }, layoutRef: layoutRef }) props.bindchange && props.bindchange(eventData) - // 更新完状态之后, 开启新的loop } /** * @desc: 获取下一个位置的索引、scrollView的contentOffset、scrollTo到的offset * @desc: 包括正循环、反向循环、不循环 - * 其中循环模式为了实现无缝链接, 会将结合contentOffset, 和 scrollTo的offset, 先scrollTo一个位置的坐标, 然后通过updateIndex设置真正的index和内容的offset,视觉上是无缝 + * 其中循环模式为了实现无缝链接, 会将结合contentOffset, 和 scrollTo的offset, + * 先scrollTo一个位置的坐标, 然后通过updateIndex设置真正的index和内容的offset,视觉上是无缝 */ function getNextConfig (scrollViewOffset: NativeScrollPoint) { const step = state.dir === 'x' ? state.width : state.height @@ -181,8 +189,11 @@ const _Carouse = forwardRef, Carouse } } return { + // 下一个要滚动到的实际元素的索引 nextIndex, + // 下一个要滚动到实际元素的offset nextOffset, + // scrollTo一个位置的坐标, 虚拟元素的位置 autoMoveOffset, isAutoEnd } @@ -216,7 +227,6 @@ const _Carouse = forwardRef, Carouse } else { if (!isAutoEnd) { scrollViewRef.current?.scrollTo({ x: nextOffset.x, y: nextOffset.y, animated: true }) - // 这里包裹了一层contentOffset需要测试下 onScrollEnd({ nativeEvent: { contentOffset: { @@ -226,23 +236,14 @@ const _Carouse = forwardRef, Carouse } } as NativeSyntheticEvent) } else { - // 同上 - setTimeout(() => { - onScrollEnd({ - nativeEvent: { - contentOffset: { - x: 0, - y: 0 - } - } - } as NativeSyntheticEvent) - }, 10) + // 安卓无法实现视觉的无缝连接, 只能回到真正的位置, 且安卓调用scrollTo不能触发onMomentumScrollEnd,还未找到为啥 if (state.dir === 'x') { scrollViewRef.current?.scrollTo({ x: step, y: step, animated: true }) - // scrollViewRef.current?.scrollTo({ x: autoMoveOffset['x'], y: autoMoveOffset['x'], animated: true }) + // scrollViewRef.current?.scrollTo({ x: autoMoveOffset.x, y: autoMoveOffset.y, animated: true }) } else { - scrollViewRef.current?.scrollTo({ x: autoMoveOffset.y, y: autoMoveOffset.y, animated: true }) + scrollViewRef.current?.scrollTo({ x: autoMoveOffset.x, y: step, animated: true }) } + updateState(0, nextOffset) } } }