Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/taro-components-advanced/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './virtual-list'
export * from './virtual-waterfall'
export * from './water-flow'
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { View } from '@tarojs/components'
import {
type CSSProperties,
type PropsWithChildren,
createContext,
createElement,
useContext,
useEffect,
useLayoutEffect,
useMemo,
useRef,
} from 'react'

import { Node, NodeEvents } from './node'
import { useMemoizedFn } from './use-memoized-fn'
import { useObservedAttr } from './use-observed-attr'
import { isWeb } from './utils'

import type { FlowItemContainerProps } from './interface'

const FlowItemContext = createContext<{ node: Node }>(Object.create(null))
export const useFlowItemPositioner = () => {
const nodeModel = useContext(FlowItemContext).node
const width$ = useObservedAttr(nodeModel, 'width')
const height$ = useObservedAttr(nodeModel, 'height')
const top$ = useObservedAttr(nodeModel, 'top')
const scrollTop$ = useObservedAttr(nodeModel, 'scrollTop')

return {
resize: useMemoizedFn(() => {
if (!isWeb()) {
nodeModel.pub(NodeEvents.Resize)
}
}),
top: top$,
scrollTop: scrollTop$,
width: width$,
height: height$,
}
}

export function FlowItemContainer({
children,
...props
}: PropsWithChildren<FlowItemContainerProps>) {
const { node } = props
const layouted$ = useObservedAttr(node, 'layouted')
const top$ = useObservedAttr(node, 'top')
const height$ = useObservedAttr(node, 'height')
const refFlowItem = useRef<HTMLElement>()

const itemStyle: CSSProperties = useMemo(() => {
const baseStyle: CSSProperties = {
width: '100%',
minHeight: node.section.defaultSize,
}
if (!layouted$) {
return baseStyle
}
Reflect.deleteProperty(baseStyle, 'minHeight')
return {
...baseStyle,
height: height$,
transition: 'transform 20ms cubic-bezier(0.075, 0.82, 0.165, 1)',
willChange: 'transform',
position: 'absolute',
top: 0,
left: 0,
transform: `translate3d(0px, ${top$}px, 0px)`,
}
}, [top$, layouted$, height$])

useEffect(() => {
let observer: ResizeObserver
if (isWeb() && typeof ResizeObserver !== 'undefined') {
observer = new ResizeObserver(() => {
node.pub(NodeEvents.Resize)
})
observer.observe(refFlowItem.current!)
}
return () => {
if (observer) {
observer.disconnect()
}
}
}, [node])

useLayoutEffect(() => {
node.measure()
}, [node])

return createElement(
View,
{ style: itemStyle, key: node.id },
createElement(
View,
{ id: node.id, ref: refFlowItem },
createElement(FlowItemContext.Provider, { value: { node } }, children)
)
)
}

export function FlowItem(props: PropsWithChildren) {
return props.children
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { View } from '@tarojs/components'
import {
type CSSProperties,
type PropsWithChildren,
Children,
createElement,
useMemo,
} from 'react'

import { FlowItemContainer } from './flow-item'
import { Section } from './section'
import { useObservedAttr } from './use-observed-attr'

import type { FlowSectionProps } from './interface'

export interface _FlowSectionProps extends FlowSectionProps {
section: Section
}

export function FlowSection({
children,
...props
}: PropsWithChildren<FlowSectionProps>) {
const {
id,
className,
style,
section,
rowGap = 0,
columnGap = 0,
} = props as _FlowSectionProps
const layouted$ = useObservedAttr(section, 'layouted')
const height$ = useObservedAttr(section, 'height')
const renderRange$ = useObservedAttr(section, 'renderRange')
const scrollTop$ = useObservedAttr(section, 'scrollTop')

const sectionStyle: CSSProperties = useMemo(() => {
const baseStyle: CSSProperties = {
display: 'flex',
flexDirection: 'row',
width: '100%',
height: height$,
gap: columnGap,
visibility: layouted$ ? 'visible' : 'hidden',
...style,
}

if (!layouted$) {
return baseStyle
}

return {
...baseStyle,
position: 'absolute',
top: 0,
transform: `translate3d(0px, ${scrollTop$}px, 0px)`,
left: 0,
}
}, [height$, style, layouted$, scrollTop$, columnGap])

const columns = useMemo(() => {
const childNodes = Children.toArray(children)
const columnStyle: CSSProperties = {
position: 'relative',
display: 'flex',
flexDirection: 'column',
gap: rowGap,
flex: 1,
}
/** 已经完成布局计算,使用虚拟滚动 */
if (layouted$) {
return renderRange$.map(([startIndex, endIndex], colIndex) => {
const columnId = `col-${colIndex}`
return createElement(
View,
{
style: columnStyle,
id: columnId,
key: columnId,
},
section.columnMap[colIndex]
.slice(startIndex, endIndex + 1)
.map((node) => {
const childNode = childNodes[node.childIndex]
const columnProps: any = {
node,
key: `${id}-item-${node.childIndex}`,
}
return createElement(FlowItemContainer, columnProps, childNode)
})
)
})
}

return section.columnMap.map((column, colIndex) => {
const columnId = `col-${colIndex}`
return createElement(
View,
{ style: columnStyle, id: columnId, key: columnId },
column.map((node) => {
const childNode = childNodes[node.childIndex]
const columnProps: any = {
node,
key: `${id}-item-${node.childIndex}`,
}
return createElement(FlowItemContainer, columnProps, childNode)
})
)
})
}, [children, layouted$, section.columnMap, renderRange$, id])

return createElement(
View,
{ style: sectionStyle, className, id: id ?? section.id },
columns
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { FlowItem, useFlowItemPositioner } from './flow-item'
export { FlowSection } from './flow-section'
export type { FlowSectionProps, WaterFlowProps } from './interface'
export { WaterFlow } from './water-flow'
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ScrollViewProps } from '@tarojs/components'

import { Node } from './node'

import type { CSSProperties } from 'react'

export interface BaseProps {
id?: string
className?: string
style?: CSSProperties
}

export interface WaterFlowProps
extends Omit<
ScrollViewProps,
'cacheExtent' | 'upperThreshold' | 'lowerThreshold' | 'style'
>,
Pick<BaseProps, 'style'> {
/**
* 距顶部多少个FlowItem时,触发 scrolltoupper 事件
* @default 0
*/
upperThresholdCount?: number
/**
* @default 0
* 距底部多少个 FlowItem时,触发 scrolltolower 事件
*/
lowerThresholdCount?: number
/**
* 设置预加载的 Item 条数。
* @default 50
*/
cacheCount?: number
}

export interface FlowSectionProps extends BaseProps {
/**
* 列数
*/
column?: number
/**
* 该分组的行间距
*/
rowGap?: number
/**
* 该分组的列间距
*/
columnGap?: number
}

export interface FlowItemContainerProps extends BaseProps {
node: Node
}

export interface Size {
width: number
height: number
}

/**
* 滚动方向
*
* - forward 向下滚动
*
* - backward 向上滚动
*/
export type ScrollDirection = 'forward' | 'backward';
Loading
Loading