-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix support for generic types in Tree component #2629
Changes from all commits
20d8328
d8a903e
b9d84ba
10eec63
54a91d0
1112d7f
30a9638
a4b1b5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,11 @@ import { IProps } from "../../common/props"; | |
import { isFunction } from "../../common/utils"; | ||
import { ITreeNode, TreeNode } from "./treeNode"; | ||
|
||
export type TreeEventHandler = (node: ITreeNode, nodePath: number[], e: React.MouseEvent<HTMLElement>) => void; | ||
export type TreeEventHandler<T = {}> = ( | ||
node: ITreeNode<T>, | ||
nodePath: number[], | ||
e: React.MouseEvent<HTMLElement>, | ||
) => void; | ||
|
||
export interface ITreeProps<T = {}> extends IProps { | ||
/** | ||
|
@@ -23,29 +27,29 @@ export interface ITreeProps<T = {}> extends IProps { | |
/** | ||
* Invoked when a node is clicked anywhere other than the caret for expanding/collapsing the node. | ||
*/ | ||
onNodeClick?: TreeEventHandler; | ||
onNodeClick?: TreeEventHandler<T>; | ||
|
||
/** | ||
* Invoked when caret of an expanded node is clicked. | ||
*/ | ||
onNodeCollapse?: TreeEventHandler; | ||
onNodeCollapse?: TreeEventHandler<T>; | ||
|
||
/** | ||
* Invoked when a node is right-clicked or the context menu button is pressed on a focused node. | ||
*/ | ||
onNodeContextMenu?: TreeEventHandler; | ||
onNodeContextMenu?: TreeEventHandler<T>; | ||
|
||
/** | ||
* Invoked when a node is double-clicked. Be careful when using this in combination with | ||
* an `onNodeClick` (single-click) handler, as the way this behaves can vary between browsers. | ||
* See http://stackoverflow.com/q/5497073/3124288 | ||
*/ | ||
onNodeDoubleClick?: TreeEventHandler; | ||
onNodeDoubleClick?: TreeEventHandler<T>; | ||
|
||
/** | ||
* Invoked when the caret of a collapsed node is clicked. | ||
*/ | ||
onNodeExpand?: TreeEventHandler; | ||
onNodeExpand?: TreeEventHandler<T>; | ||
} | ||
|
||
export class Tree<T = {}> extends React.Component<ITreeProps<T>, {}> { | ||
|
@@ -80,15 +84,16 @@ export class Tree<T = {}> extends React.Component<ITreeProps<T>, {}> { | |
return this.nodeRefs[nodeId]; | ||
} | ||
|
||
private renderNodes(treeNodes: ITreeNode[], currentPath?: number[], className?: string): JSX.Element { | ||
private renderNodes(treeNodes: Array<ITreeNode<T>>, currentPath?: number[], className?: string): JSX.Element { | ||
if (treeNodes == null) { | ||
return null; | ||
} | ||
|
||
const nodeItems = treeNodes.map((node, i) => { | ||
const elementPath = currentPath.concat(i); | ||
const TypedTreeNode = TreeNode.ofType<T>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't have to do this. can you revert this safely? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change follows the renderNodes argument type change. I believe some sort of Typed Tree Node is necessary, but I'm not sure if there is a cleaner way. |
||
return ( | ||
<TreeNode | ||
<TypedTreeNode | ||
{...node} | ||
key={node.id} | ||
contentRef={this.handleContentRef} | ||
|
@@ -101,22 +106,22 @@ export class Tree<T = {}> extends React.Component<ITreeProps<T>, {}> { | |
path={elementPath} | ||
> | ||
{this.renderNodes(node.childNodes, elementPath)} | ||
</TreeNode> | ||
</TypedTreeNode> | ||
); | ||
}); | ||
|
||
return <ul className={classNames(Classes.TREE_NODE_LIST, className)}>{nodeItems}</ul>; | ||
} | ||
|
||
private handleNodeCollapse = (node: TreeNode, e: React.MouseEvent<HTMLElement>) => { | ||
private handleNodeCollapse = (node: TreeNode<T>, e: React.MouseEvent<HTMLElement>) => { | ||
this.handlerHelper(this.props.onNodeCollapse, node, e); | ||
}; | ||
|
||
private handleNodeClick = (node: TreeNode, e: React.MouseEvent<HTMLElement>) => { | ||
private handleNodeClick = (node: TreeNode<T>, e: React.MouseEvent<HTMLElement>) => { | ||
this.handlerHelper(this.props.onNodeClick, node, e); | ||
}; | ||
|
||
private handleContentRef = (node: TreeNode, element: HTMLElement | null) => { | ||
private handleContentRef = (node: TreeNode<T>, element: HTMLElement | null) => { | ||
if (element != null) { | ||
this.nodeRefs[node.props.id] = element; | ||
} else { | ||
|
@@ -125,19 +130,19 @@ export class Tree<T = {}> extends React.Component<ITreeProps<T>, {}> { | |
} | ||
}; | ||
|
||
private handleNodeContextMenu = (node: TreeNode, e: React.MouseEvent<HTMLElement>) => { | ||
private handleNodeContextMenu = (node: TreeNode<T>, e: React.MouseEvent<HTMLElement>) => { | ||
this.handlerHelper(this.props.onNodeContextMenu, node, e); | ||
}; | ||
|
||
private handleNodeDoubleClick = (node: TreeNode, e: React.MouseEvent<HTMLElement>) => { | ||
private handleNodeDoubleClick = (node: TreeNode<T>, e: React.MouseEvent<HTMLElement>) => { | ||
this.handlerHelper(this.props.onNodeDoubleClick, node, e); | ||
}; | ||
|
||
private handleNodeExpand = (node: TreeNode, e: React.MouseEvent<HTMLElement>) => { | ||
private handleNodeExpand = (node: TreeNode<T>, e: React.MouseEvent<HTMLElement>) => { | ||
this.handlerHelper(this.props.onNodeExpand, node, e); | ||
}; | ||
|
||
private handlerHelper(handlerFromProps: TreeEventHandler, node: TreeNode, e: React.MouseEvent<HTMLElement>) { | ||
private handlerHelper(handlerFromProps: TreeEventHandler, node: TreeNode<T>, e: React.MouseEvent<HTMLElement>) { | ||
if (isFunction(handlerFromProps)) { | ||
const nodeData = Tree.nodeFromPath(node.props.path, this.props.contents); | ||
handlerFromProps(nodeData, node.props.path, e); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this change necessary? i don't think it is as this component doesn't care about the
<T>
type: it's just for the consumer. let's try to keep this as minimal as possible.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the method is called from the render method which passes in
this.props.contents
, the renderNodes function has to take in typed interfaces to render typed corresponding TypedTreeNodes. The problem that existed was that a TypedTree rendered nodes that would not be able to take in nodeData of the passed in type as expected.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 the tree nodes are rendered internally by the tree so we don't actually need the type information inside the component (it's only useful in event handlers).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But since renderNodes is the function that passes down the onClick, onContextMenu, etc. handlers, it cannot create default TreeNodes unless the event handlers match up.