Skip to content

Commit

Permalink
fix for broken inference on metadata prop (#193)
Browse files Browse the repository at this point in the history
* rfc-fix for broken inference on metadata prop

* updated fix to use type assertion instead of custom forward ref.

* fixed missing generics on node

* fixed missing generics, added default constraints for all generics

---------

Co-authored-by: Mike Ellis <[email protected]>
  • Loading branch information
mreid21 and mellis481 authored Nov 5, 2024
1 parent 98ce705 commit b633f06
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 30 deletions.
27 changes: 16 additions & 11 deletions src/TreeView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
isBranchNotSelectedAndHasOnlySelectedChild,
getOnSelectTreeAction,
getBranchNodesToExpand,
IFlatMetadata,
} from "./utils";
import { Node } from "./node";
import {
Expand Down Expand Up @@ -502,9 +503,9 @@ export interface ITreeViewOnLoadDataProps {
treeState: ITreeViewState;
}

export interface ITreeViewProps {
export interface ITreeViewProps<M extends IFlatMetadata = IFlatMetadata> {
/** Tree data*/
data: INode[];
data: INode<M>[];
/** Function called when a node changes its selected state */
onSelect?: (props: ITreeViewOnSelectProps) => void;
/** Function called when a single node is manually selected/unselected. */
Expand All @@ -517,7 +518,7 @@ export interface ITreeViewProps {
/** className to add to the outermost ul */
className?: string;
/** Render prop for the node */
nodeRenderer: (props: INodeRendererProps) => React.ReactNode;
nodeRenderer: (props: INodeRendererProps<M>) => React.ReactNode;
/** Indicates what action will be performed on a node which informs the correct aria-* properties to use on the node (aria-checked if using checkboxes, aria-selected if not). */
nodeAction?: NodeAction;
/** Array with the ids of the default expanded nodes */
Expand Down Expand Up @@ -553,8 +554,8 @@ export interface ITreeViewProps {
focusedId?: NodeId;
}

const TreeView = React.forwardRef<HTMLUListElement, ITreeViewProps>(
function TreeView(
const TreeView = React.forwardRef(
function TreeView<M extends IFlatMetadata = IFlatMetadata>(
{
data,
selectedIds,
Expand All @@ -579,8 +580,8 @@ const TreeView = React.forwardRef<HTMLUListElement, ITreeViewProps>(
focusedId,
onBlur,
...other
},
ref
}: ITreeViewProps<M>,
ref: React.ForwardedRef<HTMLUListElement>
) {
validateTreeViewData(data);
const nodeRefs = useRef({});
Expand Down Expand Up @@ -648,7 +649,7 @@ const TreeView = React.forwardRef<HTMLUListElement, ITreeViewProps>(
<Node
key={`${x}-${typeof x}`}
data={data}
element={getTreeNode(data, x)}
element={getTreeNode(data, x) as INode<M>}
setsize={getTreeParent(data).children.length}
posinset={index + 1}
level={1}
Expand All @@ -669,9 +670,12 @@ const TreeView = React.forwardRef<HTMLUListElement, ITreeViewProps>(
/>
))}
</ul>
);
}
);
)
})





const handleKeyDown = ({
data,
Expand Down Expand Up @@ -975,6 +979,7 @@ const handleKeyDown = ({
}
};


TreeView.propTypes = {
/** Tree data*/
data: PropTypes.array.isRequired,
Expand Down
25 changes: 13 additions & 12 deletions src/TreeView/node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import {
noop,
propagatedIds,
getOnSelectTreeAction,
IFlatMetadata,
} from "./utils";
import { baseClassNames, clickActions, treeTypes } from "./constants";

export interface INodeProps {
element: INode;
export interface INodeProps<M extends IFlatMetadata = IFlatMetadata> {
element: INode<M>;
dispatch: React.Dispatch<TreeViewAction>;
data: INode[];
data: INode<M>[];
nodeAction: NodeAction;
selectedIds: Set<NodeId>;
tabbableId: NodeId;
Expand All @@ -40,7 +41,7 @@ export interface INodeProps {
nodeRefs: INodeRefs;
leafRefs: INodeRefs;
baseClassNames: typeof baseClassNames;
nodeRenderer: (props: INodeRendererProps) => React.ReactNode;
nodeRenderer: (props: INodeRendererProps<M>) => React.ReactNode;
setsize: number;
posinset: number;
level: number;
Expand All @@ -53,8 +54,8 @@ export interface INodeProps {
propagateSelectUpwards: boolean;
}

export interface INodeGroupProps
extends Omit<INodeProps, "setsize" | "posinset"> {
export interface INodeGroupProps<M extends IFlatMetadata = IFlatMetadata>
extends Omit<INodeProps<M>, "setsize" | "posinset"> {
getClasses: (className: string) => string;
/** don't send this. The NodeGroup render function, determines it for you */
setsize?: undefined;
Expand All @@ -65,15 +66,15 @@ export interface INodeGroupProps
/**
* It's convenient to pass props down to the child, but we don't want to pass everything since it would create incorrect values for setsize and posinset
*/
const removeIrrelevantGroupProps = (
nodeProps: INodeProps
): Omit<INodeGroupProps, "getClasses"> => {
const removeIrrelevantGroupProps = <M extends IFlatMetadata = IFlatMetadata,>(
nodeProps: INodeProps<M>
): Omit<INodeGroupProps<M>, "getClasses"> => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { setsize, posinset, ...rest } = nodeProps;
return rest;
};

export const Node = (props: INodeProps) => {
export const Node = <M extends IFlatMetadata = IFlatMetadata>(props: INodeProps<M>) => {
const {
element,
dispatch,
Expand Down Expand Up @@ -311,15 +312,15 @@ export const Node = (props: INodeProps) => {
);
};

export const NodeGroup = ({
export const NodeGroup = <M extends IFlatMetadata = IFlatMetadata>({
data,
element,
expandedIds,
getClasses,
baseClassNames,
level,
...rest
}: INodeGroupProps) => (
}: INodeGroupProps<M>) => (
<ul role="group" className={getClasses(baseClassNames.nodeGroup)}>
{expandedIds.has(element.id) &&
element.children.length > 0 &&
Expand Down
4 changes: 2 additions & 2 deletions src/TreeView/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export type EventCallback = <T, E>(
metadata?: M;
}

export interface INodeRendererProps {
export interface INodeRendererProps<M extends IFlatMetadata = IFlatMetadata> {
/** The object that represents the rendered node */
element: INode;
element: INode<M>;
/** A function which gives back the props to pass to the node */
getNodeProps: (args?: {
onClick?: EventCallback;
Expand Down
10 changes: 5 additions & 5 deletions src/TreeView/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,15 +307,15 @@ export type IFlatMetadata = Record<
string | number | boolean | undefined | null
>;

interface ITreeNode<M extends IFlatMetadata> {
interface ITreeNode<M extends IFlatMetadata = IFlatMetadata> {
id?: NodeId;
name: string;
isBranch?: boolean;
children?: ITreeNode<M>[];
metadata?: M;
}

export const flattenTree = <M extends IFlatMetadata>(
export const flattenTree = <M extends IFlatMetadata = IFlatMetadata>(
tree: ITreeNode<M>
): INode<M>[] => {
let internalCount = 0;
Expand Down Expand Up @@ -515,8 +515,8 @@ export const getOnSelectTreeAction = (
return treeTypes.toggleSelect;
};

export const getTreeParent = (data: INode[]): INode => {
const parentNode: INode | undefined = data.find(
export const getTreeParent = <M extends IFlatMetadata = IFlatMetadata>(data: INode<M>[]): INode<M> => {
const parentNode: INode<M> | undefined = data.find(
(node) => node.parent === null
);

Expand All @@ -527,7 +527,7 @@ export const getTreeParent = (data: INode[]): INode => {
return parentNode;
};

export const getTreeNode = (data: INode[], id: NodeId): INode => {
export const getTreeNode = <M extends IFlatMetadata = IFlatMetadata>(data: INode<M>[], id: NodeId): INode<M> => {
const treeNode = data.find((node) => node.id === id);

if (treeNode == null) {
Expand Down

0 comments on commit b633f06

Please sign in to comment.