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
TypeScript
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;
}