UNPKG

react-virtualized-sticky-tree

Version:

A React component for efficiently rendering tree like structures with support for position: sticky

364 lines (363 loc) 14.1 kB
import React from 'react'; export declare enum ScrollReason { OBSERVED = "observed", REQUESTED = "requested" } /** * @Deprecated use ScrollReason */ export declare const SCROLL_REASON: typeof ScrollReason; export type NodeId = string | number; export interface TreeNode { id: NodeId; } export interface StickyTreeNode<TNodeType extends TreeNode = TreeNode> { height?: number; isSticky?: boolean; stickyTop?: number; zIndex?: number; node: TNodeType; meta?: any; } export interface EnhancedStickyTreeLeafNode<TNodeType extends TreeNode = TreeNode> extends Required<StickyTreeNode<TNodeType>> { id: NodeId; depth: number; index: number; top: number; totalHeight: number; isFirstChild: boolean; isLastChild: boolean; isLeafNode: boolean; } export interface EnhancedStickyTreeParentNode<TNodeType extends TreeNode = TreeNode> extends EnhancedStickyTreeLeafNode<TNodeType> { parentInfo: EnhancedStickyTreeLeafNode<TNodeType>; parentIndex: number; children: number[]; } export type EnhancedStickyTreeNode<TNodeType extends TreeNode = TreeNode> = EnhancedStickyTreeLeafNode<TNodeType> | EnhancedStickyTreeParentNode<TNodeType>; export type StickyTreeGetChildren<TNodeType extends TreeNode = TreeNode> = (node: TNodeType, nodeInfo: EnhancedStickyTreeNode<TNodeType>) => StickyTreeNode<TNodeType>[] | undefined; export interface StickyTreeRowRendererProps<TNodeType extends TreeNode = TreeNode, TMeta = any> { node: TNodeType; nodeInfo: EnhancedStickyTreeNode<TNodeType>; style: React.CSSProperties; meta?: TMeta; } export type StickyTreeRowRenderer<TNodeType extends TreeNode = TreeNode, TMeta = any> = (props: StickyTreeRowRendererProps<TNodeType, TMeta>) => React.ReactElement; export type StickyTreeOnScroll = (scrollInfo: { scrollTop: number; scrollLeft: number; scrollReason: ScrollReason; }) => void; export type StickyTreeOnRowsRendered<TNodeType extends TreeNode = TreeNode> = (renderInfo: { overscanStartIndex: number; overscanStopIndex: number; startIndex: number; stopIndex: number; startNode?: EnhancedStickyTreeNode<TNodeType>; endNode?: EnhancedStickyTreeNode<TNodeType>; nodes: EnhancedStickyTreeNode<TNodeType>[]; }) => void; export interface StickyTreeProps<TNodeType extends TreeNode = TreeNode, TMeta = any> { /** * Returns an array of child objects that represent the children of a particular node. * The returned object for each child should be in the form: * * { id: 'childId', height: [number], isSticky: [true|false], stickyTop: [number], zIndex: 0 } * * Where id and height are mandatory. If isSticky is true, stickyTop and zIndex should also be returned. * * Example: * * const getChildren = (id) => { * return myTree[id].children.map(childId => ({ * id: childId * isSticky: nodeIsSticky(childId), * height: myTree[childId].height, * ... * })) * } */ getChildren: StickyTreeGetChildren<TNodeType>; /** * Called to retrieve a row to render. The function should return a single React node. * The function is called with an object in the form: * * <pre> * rowRenderer({ id, style, nodeInfo }) * </pre> * * The id is the id from either the root property passed to the tree, or one returned in the getChildren call. */ rowRenderer: StickyTreeRowRenderer<TNodeType, TMeta>; /** * Allows for extra props to be passed to the rowRenderer, whilst simultaneously allowing for updates to rows for values that are apart of the model. * For example, selectedRowId, activeRowId etc. Updates to meta will no rebuild the tree, only re-render visible nodes. */ meta?: TMeta; /** * An object which represents the root node in the form: * * { * id: 'myRootNodeId', * isSticky: true * } * * This will be the first node passed to rowRenderer({id: myRootNodeId, ...}). Your id might be an index in an array or map lookup. */ root: StickyTreeNode<TNodeType>; /** * Lets StickyTree know how many rows above and below the visible area should be rendered, to improve performance. */ overscanRowCount?: number; /** * The height of the outer container. */ height: number; /** * The width of the outer container */ width: number; /** * if false, then the width and height are not set as inline styles on the sticky tree element. */ inlineWidthHeight?: boolean; /** * if true, the root node will be rendered (by calling rowRenderer() for the root id). Otherwise no root node will be rendered. */ renderRoot?: boolean; /** * Sets the position of the tree to the specified scrollTop. To reset * this, change this to -1 or undefined */ scrollTop?: number; /** * Sets the position of the tree to the specified scrollIndex. This is useful when * paired with onRowsRendered() which returns the startIndex and stopIndex. */ scrollIndex?: number; /** * Called whenever the scrollTop position changes. */ onScroll?: StickyTreeOnScroll; /** * Called to indicate that a new render range for rows has been rendered. */ onRowsRendered?: StickyTreeOnRowsRendered<TNodeType>; /** * Specifies the default row height which will be used if the child or root object do not have a height specified. */ rowHeight?: number; /** * If true, all leaf nodes will be wrapped with a div, even when they are not sticky. this may help with certain tree structures where you need a constant key * for the element so that it is not recreated when React dom diffing occurs. */ wrapAllLeafNodes?: boolean; /** * If true, we can make some assumptions about the results returned by getChildren() which improve rendering performance. */ isModelImmutable?: boolean; /** * Returns a reference to the tree so the API can be used on the tree. * @param tree */ apiRef?: (tree: StickyTree<TNodeType>) => void; } export interface StickyTreeState { scrollTop: number; currNodePos: number; scrollTick: boolean; scrollReason?: ScrollReason; } export interface RowRenderRange { start: number; end: number; visibleStart: number; visibleEnd: number; } export default class StickyTree<TNodeType extends TreeNode = TreeNode, TMeta = any> extends React.PureComponent<StickyTreeProps<TNodeType, TMeta>, StickyTreeState> { static defaultProps: { overscanRowCount: number; renderRoot: boolean; wrapAllLeafNodes: boolean; isModelImmutable: boolean; }; private nodes; private getChildrenCache; private rowRenderCache; private rowRenderRange?; private structureChanged; private elemRef; private pendingScrollTop?; private treeToRender; constructor(props: StickyTreeProps<TNodeType, TMeta>); /** * Converts the consumer's tree structure into a flat array with root at index: 0, * including information about the top and height of each node. * * i.e: * [ * { id: 'root', top: 0, index: 0, height: 100. isSticky: true , zIndex: 0, stickyTop: 10 }, * { id: 'child1', top: 10, index: 1, parentIndex: 0 height: 10, isSticky: false }, * ... * ] */ private flattenTree; private getBranchChildrenIds; UNSAFE_componentWillMount(): void; private treeDataUpdated; UNSAFE_componentWillReceiveProps(newProps: StickyTreeProps<TNodeType>): void; UNSAFE_componentWillUpdate(newProps: StickyTreeProps<TNodeType>, newState: StickyTreeState): void; getNode(nodeId: NodeId): TNodeType; /** * Returns the index of the node in a flat list tree (post-order traversal). * * @param nodeId The node index to get the index for. * @returns {number} */ getNodeIndex(nodeId: NodeId): number; /** * Returns the node that appears higher than this node (either a parent, sibling or child of the sibling above). * @param nodeId The node to get the previous node of. * @returns {*} */ getPreviousNodeId(nodeId: NodeId): NodeId | undefined; /** * Returns the node that appears lower than this node (sibling or sibling of the node's parent). * @param nodeId The node to get the next node of. * @returns {*} */ getNextNodeId(nodeId: NodeId): NodeId | undefined; /** * Returns true if the node is completely visible and is not obscured. * This will return false when the node is partially obscured. * * @param nodeId The id of the node to check * @param includeObscured if true, this method will return true for partially visible nodes. * @returns {boolean} */ isNodeVisible(nodeId: NodeId, includeObscured?: boolean): boolean; /** * Returns true if the node is completely visible and is not obscured, unless includeObscured is specified. * This will return false when the node is partially obscured, unless includeObscured is set to true. * * @param index The index of the node to check, generally retrieved via getNodeIndex() * @param includeObscured if true, this method will return true for partially visible nodes. * @returns {boolean} */ isIndexVisible(index: number, includeObscured?: boolean): boolean; /** * Returns true if the node is within the view port window. Note this this will return FALSE for visible sticky nodes that are * partially out of view disregarding sticky, which is useful when the node will become unstuck. This may occur when the node is * collapsed in a tree. In this case, you want to scroll this node back into view so that the collapsed node stays in the same position. * * @param nodeId The id of the node to check * @returns {boolean} */ isNodeInViewport(nodeId: NodeId): boolean; /** * Returns true if the node is within the view port window. Note this this will return FALSE for visible sticky nodes that are * partially out of view disregarding sticky, which is useful when the node will become unstuck. This may occur when the node is * collapsed in a tree. In this case, you want to scroll this node back into view so that the collapsed node stays in the same position. * * This also returns false if the node is partially out of view. * * @param index The node index, generally retrieved via getNodeIndex() * @returns {boolean} */ isIndexInViewport(index: number): boolean; /** * Returns the top of the node with the specified id. * @param nodeId */ getNodeTop(nodeId: NodeId): number; /** * Returns the top of the node with the specified index. * @param index */ getIndexTop(index: number): number; /** * Returns the scrollTop of the scrollable element * * @return returns -1 if the elem does not exist. */ getScrollTop(): number; /** * Returns the scrollLeft of the scrollable element * * @return returns -1 if the elem does not exist. */ getScrollLeft(): number; /** * Sets the scrollTop position of the scrollable element. * @param scrollTop */ setScrollTop(scrollTop: number): void; /** * Sets the scrollLeft position of the scrollable element. * @param scrollLeft */ setScrollLeft(scrollLeft: number): void; /** * Scrolls the node into view so that it is visible. * * @param nodeId The node id of the node to scroll into view. * @param alignToTop if true, the node will aligned to the top of viewport, or sticky parent. If false, the bottom of the node will * be aligned with the bottom of the viewport. */ scrollNodeIntoView(nodeId: NodeId, alignToTop?: boolean): void; /** * Scrolls the node into view so that it is visible. * * @param index The index of the node. * @param alignToTop if true, the node will aligned to the top of viewport, or sticky parent. If false, the bottom of the node will * be aligned with the bottom of the viewport. */ scrollIndexIntoView(index: number, alignToTop?: boolean): void; componentDidUpdate(prevProps: StickyTreeProps<TNodeType>, prevState: StickyTreeState): void; private refreshCachedMetadata; recomputeTree(): void; private storeRenderTree; forceUpdate(): void; private renderParentTree; private renderParentContainer; private getChildContainerStyle; private renderChildWithChildren; private getClientNodeStyle; private getClientLeafNodeStyle; private renderChildren; private renderNode; /** * Determines the start and end number of the range to be rendered. * @returns {{start: number, end: number}} Indexes within nodes */ private getRenderRowRange; /** * Returns the parent path from nodes for the specified index within nodes. * @param nodeIndex * @param topDownOrder if true, the array with index 0 will be the root node, otherwise 0 will be the immediate parent. * @returns {Array<TreeNode>} */ private getParentPath; /** * Searches from the current node position downwards to see if the top of nodes above are greater * than or equal to the current scrollTop * @param scrollTop * @param searchPos * @returns {number} */ private forwardSearch; /** * Searches from the current node position upwards to see if the top of nodes above are less than * or equal the current scrollTop. * @param scrollTop * @param searchPos * @returns {number} */ private backwardSearch; /** * Sets the scroll top in state and finds and sets the closest node to that scroll top. */ private setScrollTopAndClosestNode; private onScroll; render(): React.JSX.Element; }