diff --git a/libs/extension/src/components/VList.less b/libs/extension/src/components/VList.less new file mode 100644 index 00000000..8c14e471 --- /dev/null +++ b/libs/extension/src/components/VList.less @@ -0,0 +1,11 @@ +.container { + position: relative; + overflow-y: auto; + height: 100%; + width: 100%; +} + +.item { + position: absolute; + width: 100%; +} \ No newline at end of file diff --git a/libs/extension/src/components/VList.tsx b/libs/extension/src/components/VList.tsx new file mode 100644 index 00000000..84b71943 --- /dev/null +++ b/libs/extension/src/components/VList.tsx @@ -0,0 +1,78 @@ + +import { useState, useRef, useEffect } from 'horizon'; +import styles from './VList.less'; + +interface IProps { + data: T[], + width: number, // 暂时未用到,当需要支持横向滚动时使用 + height: number, // VList 的高度 + children: any, // horizon 组件,组件类型是 T + itemHeight: number, + scrollIndex?: number, + onRendered:(renderInfo: renderInfoType) => void; + filter?(data: T): boolean, // false 表示该行不显示 +} + +const defaultRenderInfo = { + visibleItems: ([] as string[]) +}; + +export type renderInfoType = typeof defaultRenderInfo; + +export function VList(props: IProps) { + const { + data, + height, + children, + itemHeight, + scrollIndex = 0, + filter, + onRendered, + } = props; + const [scrollTop, setScrollTop] = useState(scrollIndex * itemHeight); + const renderInfo = useRef({visibleItems: []}); + useEffect(() => { + onRendered(renderInfo.current); + }); + + const handleScroll = (event: any) => { + const scrollTop = event.target.scrollTop; + setScrollTop(scrollTop); + }; + const showList: any[] = []; + let totalHeight = 0; + // 顶部冗余 + const startShowTopValue = Math.max(scrollTop - itemHeight * 4, 0); + // 底部冗余 + const showNum = Math.floor(height / itemHeight) + 4; + // 如果最后一个显示不全,不统计在显示 ids 内 + const maxTop = scrollTop + height - itemHeight; + // 清空记录的上次渲染的数据 + renderInfo.current.visibleItems.length = 0; + data.forEach((item, i) => { + if (filter && !filter(item)) { + return; + } + if (totalHeight >= startShowTopValue && showList.length <= showNum) { + showList.push( +
+ {children(i, item)} +
+ ); + if (totalHeight >= scrollTop && totalHeight < maxTop) { + renderInfo.current.visibleItems.push(item); + } + } + totalHeight += itemHeight; + }); + + return ( +
+ {showList} +
+
+ ); +} diff --git a/libs/extension/src/components/VTree.less b/libs/extension/src/components/VTree.less index 1460ce0f..0f34f9cd 100644 --- a/libs/extension/src/components/VTree.less +++ b/libs/extension/src/components/VTree.less @@ -1,10 +1,7 @@ @import 'assets.less'; .treeContainer { - position: relative; - width: 100%; height: 100%; - overflow-y: auto; .treeItem { width: 100%; diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 86f1daf7..5b04385e 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -1,7 +1,9 @@ -import { useState } from 'horizon'; +import { useState, useEffect } from 'horizon'; import styles from './VTree.less'; -import Arrow from '../svgs/Arrow'; +import Triangle from '../svgs/Triangle'; import { createRegExp } from './../utils'; +import { SizeObserver } from './SizeObserver'; +import { renderInfoType, VList } from './VList'; export interface IData { id: string; @@ -11,7 +13,6 @@ export interface IData { } type IItem = { - style: any, hasChild: boolean, onCollapse: (id: string) => void, onClick: (id: string) => void, @@ -20,15 +21,11 @@ type IItem = { highlightValue: string, } & IData -// TODO: 计算可以展示的最多数量,并且监听显示器高度变化修改数值 -const showNum = 70; -const lineHeight = 18; const indentationLength = 20; function Item(props: IItem) { const { name, - style, userKey, hasChild, onCollapse, @@ -37,17 +34,17 @@ function Item(props: IItem) { indentation, onClick, isSelect, - highlightValue, + highlightValue = '', } = props; const isShowKey = userKey !== ''; - const showIcon = hasChild ? : ''; + const showIcon = hasChild ? : ''; const handleClickCollapse = () => { onCollapse(id); }; const handleClick = () => { onClick(id); }; - const itemAttr: any = { style, className: styles.treeItem, onClick: handleClick }; + const itemAttr: any = { className: styles.treeItem, onClick: handleClick }; if (isSelect) { itemAttr.tabIndex = 0; itemAttr.className = styles.treeItem + ' ' + styles.select; @@ -93,10 +90,17 @@ function Item(props: IItem) { ); } -function VTree({ data, highlightValue }: { data: IData[], highlightValue: string }) { - const [scrollTop, setScrollTop] = useState(0); +function VTree({ data, highlightValue, selectedId, onRendered }: { + data: IData[], + highlightValue: string, + selectedId: number, + onRendered: (renderInfo: renderInfoType) => void +}) { const [collapseNode, setCollapseNode] = useState(new Set()); - const [selectItem, setSelectItem] = useState(); + const [selectItem, setSelectItem] = useState(selectedId); + useEffect(() => { + setSelectItem(selectedId); + }, [selectedId]); const changeCollapseNode = (id: string) => { const nodes = new Set(); collapseNode.forEach(value => { @@ -112,17 +116,14 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string const handleClickItem = (id: string) => { setSelectItem(id); }; - const showList: any[] = []; - let totalHeight = 0; let currentCollapseIndentation: null | number = null; - data.forEach((item, index) => { - // 存在未处理完的收起节点 + // 过滤掉折叠的 item,不展示在 VList 中 + const filter = (item: IData) => { if (currentCollapseIndentation !== null) { - const indentation = item.indentation; // 缩进更大,不显示 - if (indentation > currentCollapseIndentation) { - return; + if (item.indentation > currentCollapseIndentation) { + return false; } else { // 缩进小,说明完成了该收起节点的子节点处理。 currentCollapseIndentation = null; @@ -130,44 +131,45 @@ function VTree({ data, highlightValue }: { data: IData[], highlightValue: string } const id = item.id; const isCollapsed = collapseNode.has(id); - if (totalHeight >= scrollTop && showList.length <= showNum) { - const nextItem = data[index + 1]; - // 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标 - const hasChild = nextItem ? nextItem.indentation > item.indentation : false; - showList.push( - - ); - } - totalHeight = totalHeight + lineHeight; if (isCollapsed) { // 该节点需要收起子节点 currentCollapseIndentation = item.indentation; } - }); - - const handleScroll = (event: any) => { - const scrollTop = event.target.scrollTop; - // 顶部留 100px 冗余高度 - setScrollTop(Math.max(scrollTop - 100, 0)); + return true; }; return ( -
- {showList} - {/* 确保有足够的高度 */} -
-
+ + {(width, height) => { + return ( + + {(index: number, item: IData) => { + // 如果存在下一个节点,并且节点缩进比自己大,说明下个节点是子节点,节点本身需要显示展开收起图标 + const nextItem = data[index + 1]; + const hasChild = nextItem && nextItem.indentation > item.indentation; + return ( + + ); + }} + + ); + }} + ); }