Skip to content

Commit 927b56b

Browse files
author
chenjiajun79
committed
feat(taro-components-advanced): 新增 waterflow 组件
1 parent 5c8c031 commit 927b56b

File tree

14 files changed

+1661
-0
lines changed

14 files changed

+1661
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './virtual-list'
22
export * from './virtual-waterfall'
3+
export * from './water-flow'
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { View } from '@tarojs/components'
2+
import {
3+
type CSSProperties,
4+
type PropsWithChildren,
5+
createContext,
6+
createElement,
7+
useContext,
8+
useEffect,
9+
useLayoutEffect,
10+
useMemo,
11+
useRef,
12+
} from 'react'
13+
14+
import { Node, NodeEvents } from './node'
15+
import { useMemoizedFn } from './use-memoized-fn'
16+
import { useObservedAttr } from './use-observed-attr'
17+
import { isWeb } from './utils'
18+
19+
import type { FlowItemContainerProps } from './interface'
20+
21+
const FlowItemContext = createContext<{ node: Node }>(Object.create(null))
22+
export const useFlowItemPositioner = () => {
23+
const nodeModel = useContext(FlowItemContext).node
24+
const width$ = useObservedAttr(nodeModel, 'width')
25+
const height$ = useObservedAttr(nodeModel, 'height')
26+
const top$ = useObservedAttr(nodeModel, 'top')
27+
const scrollTop$ = useObservedAttr(nodeModel, 'scrollTop')
28+
29+
return {
30+
resize: useMemoizedFn(() => {
31+
if (!isWeb()) {
32+
nodeModel.pub(NodeEvents.Resize)
33+
}
34+
}),
35+
top: top$,
36+
scrollTop: scrollTop$,
37+
width: width$,
38+
height: height$,
39+
}
40+
}
41+
42+
export function FlowItemContainer({
43+
children,
44+
...props
45+
}: PropsWithChildren<FlowItemContainerProps>) {
46+
const { node } = props
47+
const layouted$ = useObservedAttr(node, 'layouted')
48+
const top$ = useObservedAttr(node, 'top')
49+
const height$ = useObservedAttr(node, 'height')
50+
const refFlowItem = useRef<HTMLElement>()
51+
52+
const itemStyle: CSSProperties = useMemo(() => {
53+
const baseStyle: CSSProperties = {
54+
width: '100%',
55+
minHeight: node.section.defaultSize,
56+
}
57+
if (!layouted$) {
58+
return baseStyle
59+
}
60+
Reflect.deleteProperty(baseStyle, 'minHeight')
61+
return {
62+
...baseStyle,
63+
height: height$,
64+
transition: 'transform 20 cubic-bezier(0.075, 0.82, 0.165, 1)',
65+
willChange: 'transform',
66+
position: 'absolute',
67+
top: 0,
68+
left: 0,
69+
transform: `translate3d(0px, ${top$}px, 0px)`,
70+
}
71+
}, [top$, layouted$, height$])
72+
73+
useEffect(() => {
74+
let observer: ResizeObserver
75+
if (isWeb() && ResizeObserver) {
76+
observer = new ResizeObserver(() => {
77+
node.pub(NodeEvents.Resize)
78+
})
79+
observer.observe(refFlowItem.current!)
80+
}
81+
return () => {
82+
if (observer) {
83+
observer.disconnect()
84+
}
85+
}
86+
}, [])
87+
88+
useLayoutEffect(() => {
89+
node.measure()
90+
}, [node])
91+
92+
return createElement(
93+
View,
94+
{ style: itemStyle, key: node.id },
95+
createElement(
96+
View,
97+
{ id: node.id, ref: refFlowItem },
98+
createElement(FlowItemContext.Provider, { value: { node } }, children)
99+
)
100+
)
101+
}
102+
103+
export function FlowItem(props: PropsWithChildren) {
104+
return props.children
105+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { View } from '@tarojs/components'
2+
import {
3+
type CSSProperties,
4+
type PropsWithChildren,
5+
Children,
6+
createElement,
7+
useMemo,
8+
} from 'react'
9+
10+
import { FlowItemContainer } from './flow-item'
11+
import { Section } from './section'
12+
import { useObservedAttr } from './use-observed-attr'
13+
14+
import type { FlowSectionProps } from './interface'
15+
16+
export interface _FlowSectionProps extends FlowSectionProps {
17+
section: Section
18+
}
19+
20+
export function FlowSection({
21+
children,
22+
...props
23+
}: PropsWithChildren<FlowSectionProps>) {
24+
const {
25+
id,
26+
className,
27+
style,
28+
section,
29+
rowGap = 0,
30+
columnGap = 0,
31+
} = props as _FlowSectionProps
32+
const layouted$ = useObservedAttr(section, 'layouted')
33+
const height$ = useObservedAttr(section, 'height')
34+
const renderRange$ = useObservedAttr(section, 'renderRange')
35+
const scrollTop$ = useObservedAttr(section, 'scrollTop')
36+
37+
const sectionStyle: CSSProperties = useMemo(() => {
38+
const baseStyle: CSSProperties = {
39+
display: 'flex',
40+
flexDirection: 'row',
41+
width: '100%',
42+
height: height$,
43+
position: 'absolute',
44+
gap: columnGap,
45+
visibility: layouted$ ? 'visible' : 'hidden',
46+
transition: 'transform 50 cubic-bezier(0.075, 0.82, 0.165, 1)',
47+
willChange: 'transform',
48+
...style,
49+
}
50+
51+
if (!layouted$) {
52+
return baseStyle
53+
}
54+
55+
return {
56+
...baseStyle,
57+
top: 0,
58+
transform: `translate3d(0px, ${scrollTop$}px, 0px)`,
59+
left: 0,
60+
}
61+
}, [height$, style, layouted$, scrollTop$, columnGap])
62+
63+
const columns = useMemo(() => {
64+
const childNodes = Children.toArray(children)
65+
const columnStyle: CSSProperties = {
66+
position: 'relative',
67+
display: 'flex',
68+
flexDirection: 'column',
69+
gap: rowGap,
70+
flex: 1,
71+
}
72+
/** 已经完成布局计算,使用虚拟滚动 */
73+
if (layouted$) {
74+
return renderRange$.map(([startIndex, endIndex], colIndex) => {
75+
const columnId = `col-${colIndex}`
76+
return createElement(
77+
View,
78+
{
79+
style: columnStyle,
80+
id: columnId,
81+
key: columnId,
82+
},
83+
section.columnMap[colIndex]
84+
.slice(startIndex, endIndex + 1)
85+
.map((node) => {
86+
const childNode = childNodes[node.childIndex]
87+
const columnProps: any = {
88+
node,
89+
key: `${id}-item-${node.childIndex}`,
90+
}
91+
return createElement(FlowItemContainer, columnProps, childNode)
92+
})
93+
)
94+
})
95+
}
96+
97+
return section.columnMap.map((column, colIndex) => {
98+
const columnId = `col-${colIndex}`
99+
return createElement(
100+
View,
101+
{ style: columnStyle, id: columnId, key: columnId },
102+
column.map((node) => {
103+
const childNode = childNodes[node.childIndex]
104+
const columnProps: any = {
105+
node,
106+
key: `${id}-item-${node.childIndex}`,
107+
}
108+
return createElement(FlowItemContainer, columnProps, childNode)
109+
})
110+
)
111+
})
112+
}, [children, layouted$, section.columnMap, renderRange$, id])
113+
114+
return createElement(
115+
View,
116+
{ style: sectionStyle, className, id: id ?? section.id },
117+
columns
118+
)
119+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { FlowItem, useFlowItemPositioner } from './flow-item'
2+
export { FlowSection } from './flow-section'
3+
export type { FlowSectionProps, WaterFlowProps } from './interface'
4+
export { WaterFlow } from './water-flow'
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Node } from './node'
2+
3+
import type { ScrollViewProps } from '@tarojs/components'
4+
import type { CSSProperties } from 'react'
5+
6+
export interface BaseProps {
7+
id?: string
8+
className?: string
9+
style?: CSSProperties
10+
}
11+
12+
export interface WaterFlowProps
13+
extends Omit<
14+
ScrollViewProps,
15+
'cacheExtent' | 'upperThreshold' | 'lowerThreshold' | 'style'
16+
>,
17+
Pick<BaseProps, 'style'> {
18+
/**
19+
* 距顶部多少个FlowItem时,触发 scrolltoupper 事件
20+
* @default 0
21+
*/
22+
upperThresholdCount?: number
23+
/**
24+
* @default 0
25+
* 距底部多少个 FlowItem时,触发 scrolltolower 事件
26+
*/
27+
lowerThresholdCount?: number
28+
/**
29+
* 设置预加载的 Item 条数。
30+
* @default 50
31+
*/
32+
cacheCount?: number
33+
}
34+
35+
export interface FlowSectionProps extends BaseProps {
36+
/**
37+
* 列数
38+
*/
39+
column?: number
40+
/**
41+
* 该分组的行间距
42+
*/
43+
rowGap?: number
44+
/**
45+
* 该分组的列间距
46+
*/
47+
columnGap?: number
48+
}
49+
50+
export interface FlowItemContainerProps extends BaseProps {
51+
node: Node
52+
}
53+
54+
export interface Size {
55+
width: number
56+
height: number
57+
}
58+
59+
/**
60+
* 滚动方向
61+
*
62+
* - forward 向下滚动
63+
*
64+
* - backward 向上滚动
65+
*/
66+
export type ScrollDirection = 'forward' | 'backward';

0 commit comments

Comments
 (0)