Skip to content

Commit

Permalink
Add Nodes To Handlers (#60)
Browse files Browse the repository at this point in the history
- Remove extends IdObj since we have an idAccessor prop
- Add the Node objects to the handlers where appropriate
  • Loading branch information
jameskerr authored Nov 2, 2022
1 parent 9cb377e commit fb73338
Show file tree
Hide file tree
Showing 23 changed files with 92 additions and 69 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ function App() {
These are all the props you can pass to the Tree component.

```ts
interface TreeProps<T extends IdObj> {
interface TreeProps<T> {
/* Data Options */
data?: T[];
initialData?: T[];
Expand Down Expand Up @@ -327,7 +327,7 @@ interface TreeProps<T extends IdObj> {
The _\<RowRenderer\>_ is responsible for attaching the drop ref, the row style (top, height) and the aria-attributes. The default should work fine for most use cases, but it can be replaced by your own component if you need. See the _renderRow_ prop in the _\<Tree\>_ component.

```ts
type RowRendererProps<T extends IdObj> = {
type RowRendererProps<T> = {
node: NodeApi<T>;
innerRef: (el: HTMLDivElement | null) => void;
attrs: HTMLAttributes<any>;
Expand All @@ -342,7 +342,7 @@ The _\<NodeRenderer\>_ is responsible for attaching the drag ref, the node style
There is a default renderer, but it's only there as a placeholder to get started. You'll want to create your own component for this. It is passed as the _\<Tree\>_ components only child.

```ts
export type NodeRendererProps<T extends IdObj> = {
export type NodeRendererProps<T> = {
style: CSSProperties;
node: NodeApi<T>;
tree: TreeApi<T>;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-arborist",
"version": "2.0.0",
"version": "2.1.0",
"license": "MIT",
"source": "src/index.ts",
"main": "dist/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function Count(props: { count: number; mouse: XYCoord | null }) {
else return null;
}

const PreviewNode = memo(function PreviewNode<T extends IdObj>(props: {
const PreviewNode = memo(function PreviewNode<T>(props: {
id: string | null;
dragIds: string[];
}) {
Expand Down
6 changes: 3 additions & 3 deletions packages/react-arborist/src/components/default-node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useRef } from "react";
import { NodeRendererProps } from "../types/renderers";
import { IdObj } from "../types/utils";

export function DefaultNode<T extends IdObj>(props: NodeRendererProps<T>) {
export function DefaultNode<T>(props: NodeRendererProps<T>) {
return (
<div ref={props.dragHandle} style={props.style}>
<span
Expand All @@ -18,7 +18,7 @@ export function DefaultNode<T extends IdObj>(props: NodeRendererProps<T>) {
);
}

function Show<T extends IdObj>(props: NodeRendererProps<T>) {
function Show<T>(props: NodeRendererProps<T>) {
return (
<>
{/* @ts-ignore */}
Expand All @@ -27,7 +27,7 @@ function Show<T extends IdObj>(props: NodeRendererProps<T>) {
);
}

function Edit<T extends IdObj>({ node }: NodeRendererProps<T>) {
function Edit<T>({ node }: NodeRendererProps<T>) {
const input = useRef<any>();

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/src/components/default-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { RowRendererProps } from "../types/renderers";
import { IdObj } from "../types/utils";

export function DefaultRow<T extends IdObj>({
export function DefaultRow<T>({
node,
attrs,
innerRef,
Expand Down
4 changes: 2 additions & 2 deletions packages/react-arborist/src/components/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ import { TreeProps } from "../types/tree-props";
import { createStore, Store } from "redux";
import { actions as visibility } from "../state/open-slice";

type Props<T extends IdObj> = {
type Props<T> = {
treeProps: TreeProps<T>;
imperativeHandle: React.Ref<TreeApi<T> | undefined>;
children: ReactNode;
};

const SERVER_STATE = initialState();

export function TreeProvider<T extends IdObj>({
export function TreeProvider<T>({
treeProps,
imperativeHandle,
children,
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/src/components/row-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Props = {
index: number;
};

export const RowContainer = React.memo(function RowContainer<T extends IdObj>({
export const RowContainer = React.memo(function RowContainer<T>({
index,
style,
}: Props) {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/src/components/tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { TreeProps } from "../types/tree-props";
import { IdObj } from "../types/utils";
import { useValidatedProps } from "../hooks/use-validated-props";

export const Tree = forwardRef(function Tree<T extends IdObj>(
export const Tree = forwardRef(function Tree<T>(
props: TreeProps<T>,
ref: React.Ref<TreeApi<T> | undefined>
) {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IdObj } from "./types/utils";

export const TreeApiContext = createContext<TreeApi<any> | null>(null);

export function useTreeApi<T extends IdObj>() {
export function useTreeApi<T>() {
const value = useContext<TreeApi<T> | null>(
TreeApiContext as unknown as React.Context<TreeApi<T> | null>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/src/data/create-index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NodeApi } from "../interfaces/node-api";
import { IdObj } from "../types/utils";

export const createIndex = <T extends IdObj>(nodes: NodeApi<T>[]) => {
export const createIndex = <T>(nodes: NodeApi<T>[]) => {
return nodes.reduce<{ [id: string]: number }>((map, node, index) => {
map[node.id] = index;
return map;
Expand Down
6 changes: 3 additions & 3 deletions packages/react-arborist/src/data/create-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { NodeApi } from "../interfaces/node-api";
import { TreeApi } from "../interfaces/tree-api";
import { IdObj } from "../types/utils";

export function createList<T extends IdObj>(tree: TreeApi<T>) {
export function createList<T>(tree: TreeApi<T>) {
if (tree.isFiltered) {
return flattenAndFilterTree(tree.root, tree.isMatch.bind(tree));
} else {
return flattenTree(tree.root);
}
}

function flattenTree<T extends IdObj>(root: NodeApi<T>): NodeApi<T>[] {
function flattenTree<T>(root: NodeApi<T>): NodeApi<T>[] {
const list: NodeApi<T>[] = [];
function collect(node: NodeApi<T>) {
if (node.level >= 0) {
Expand All @@ -25,7 +25,7 @@ function flattenTree<T extends IdObj>(root: NodeApi<T>): NodeApi<T>[] {
return list;
}

function flattenAndFilterTree<T extends IdObj>(
function flattenAndFilterTree<T>(
root: NodeApi<T>,
isMatch: (n: NodeApi<T>) => boolean
): NodeApi<T>[] {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/src/data/create-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TreeApi } from "../interfaces/tree-api";

export const ROOT_ID = "__REACT_ARBORIST_INTERNAL_ROOT__";

export function createRoot<T extends IdObj>(tree: TreeApi<T>): NodeApi<T> {
export function createRoot<T>(tree: TreeApi<T>): NodeApi<T> {
function visitSelfAndChildren(
data: T,
level: number,
Expand Down
10 changes: 5 additions & 5 deletions packages/react-arborist/src/data/simple-tree.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type IdObj = { id: string; children?: IdObj[] };
type SimpleData = { id: string; name: string; children?: SimpleData[] };

export class SimpleTree<T extends IdObj> {
export class SimpleTree<T extends SimpleData> {
root: SimpleNode<T>;
constructor(data: T[]) {
this.root = createRoot<T>(data);
Expand Down Expand Up @@ -48,20 +48,20 @@ export class SimpleTree<T extends IdObj> {
}
}

function createRoot<T extends IdObj>(data: T[]) {
function createRoot<T extends SimpleData>(data: T[]) {
const root = new SimpleNode<T>({ id: "ROOT" } as T, null);
root.children = data.map((d) => createNode(d as T, root));
return root;
}

function createNode<T extends IdObj>(data: T, parent: SimpleNode<T>) {
function createNode<T extends SimpleData>(data: T, parent: SimpleNode<T>) {
const node = new SimpleNode<T>(data, parent);
if (data.children)
node.children = data.children.map((d) => createNode<T>(d as T, node));
return node;
}

class SimpleNode<T extends IdObj> {
class SimpleNode<T extends SimpleData> {
id: string;
children?: SimpleNode<T>[];
constructor(public data: T, public parent: SimpleNode<T> | null) {
Expand Down
9 changes: 5 additions & 4 deletions packages/react-arborist/src/dnd/drag-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import { actions as dnd } from "../state/dnd-slice";
import { safeRun } from "../utils";
import { ROOT_ID } from "../data/create-root";

export function useDragHook<T extends IdObj>(
node: NodeApi<T>
): ConnectDragSource {
export function useDragHook<T>(node: NodeApi<T>): ConnectDragSource {
const tree = useTreeApi();
const ids = tree.selectedIds;
const [_, ref, preview] = useDrag<DragItem, DropResult, void>(
Expand All @@ -33,10 +31,13 @@ export function useDragHook<T extends IdObj>(
// If they held down meta, we need to create a copy
// if (drop.dropEffect === "copy")
if (drop && drop.parentId) {
const parentId = drop.parentId === ROOT_ID ? null : drop.parentId;
safeRun(tree.props.onMove, {
dragIds: item.dragIds,
parentId: drop.parentId === ROOT_ID ? null : drop.parentId,
parentId,
index: drop.index,
dragNodes: item.dragIds.map((id) => tree.get(id)!),
parentNode: tree.get(drop.parentId),
});
tree.open(drop.parentId);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/src/hooks/use-fresh-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useMemo } from "react";
import { useTreeApi } from "../context";
import { IdObj } from "../types/utils";

export function useFreshNode<T extends IdObj>(index: number) {
export function useFreshNode<T>(index: number) {
const tree = useTreeApi<T>();
const original = tree.at(index);
if (!original) throw new Error(`Could not find node for index: ${index}`);
Expand Down
19 changes: 12 additions & 7 deletions packages/react-arborist/src/hooks/use-simple-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ export type SimpleTreeData = {

let nextId = 0;

export function useSimpleTree<T extends IdObj>(initialData: T[]) {
export function useSimpleTree<T>(initialData: T[]) {
const [data, setData] = useState(initialData);
const tree = useMemo(() => new SimpleTree<T>(data), [data]);

const onMove: MoveHandler = (args: {
const tree = useMemo(
() =>
new SimpleTree<// @ts-ignore
T>(data),
[data]
);

const onMove: MoveHandler<T> = (args: {
dragIds: string[];
parentId: null | string;
index: number;
Expand All @@ -31,20 +36,20 @@ export function useSimpleTree<T extends IdObj>(initialData: T[]) {
setData(tree.data);
};

const onRename: RenameHandler = ({ name, id }) => {
const onRename: RenameHandler<T> = ({ name, id }) => {
tree.update({ id, changes: { name } as any });
setData(tree.data);
};

const onCreate: CreateHandler = ({ parentId, index, type }) => {
const onCreate: CreateHandler<T> = ({ parentId, index, type }) => {
const data = { id: `simple-tree-id-${nextId++}`, name: "" } as any;
if (type === "internal") data.children = [];
tree.create({ parentId, index, data });
setData(tree.data);
return data;
};

const onDelete: DeleteHandler = (args: { ids: string[] }) => {
const onDelete: DeleteHandler<T> = (args: { ids: string[] }) => {
args.ids.forEach((id) => tree.drop({ id }));
setData(tree.data);
};
Expand Down
4 changes: 1 addition & 3 deletions packages/react-arborist/src/hooks/use-validated-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { TreeProps } from "../types/tree-props";
import { IdObj } from "../types/utils";
import { SimpleTreeData, useSimpleTree } from "./use-simple-tree";

export function useValidatedProps<T extends IdObj>(
props: TreeProps<T>
): TreeProps<T> {
export function useValidatedProps<T>(props: TreeProps<T>): TreeProps<T> {
if (props.initialData && props.data) {
throw new Error(
`React Arborist Tree => Provide either a data or initialData prop, but not both.`
Expand Down
4 changes: 2 additions & 2 deletions packages/react-arborist/src/interfaces/node-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TreeApi } from "./tree-api";
import { IdObj } from "../types/utils";
import { ROOT_ID } from "../data/create-root";

type Params<T extends IdObj> = {
type Params<T> = {
id: string;
data: T;
level: number;
Expand All @@ -15,7 +15,7 @@ type Params<T extends IdObj> = {
tree: TreeApi<T>;
};

export class NodeApi<T extends IdObj = IdObj> {
export class NodeApi<T = any> {
tree: TreeApi<T>;
id: string;
data: T;
Expand Down
41 changes: 26 additions & 15 deletions packages/react-arborist/src/interfaces/tree-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { createList } from "../data/create-list";
import { createIndex } from "../data/create-index";

const { safeRun, identify, identifyNull } = utils;
export class TreeApi<T extends IdObj> {
export class TreeApi<T> {
static editPromise: null | ((args: EditResult) => void);
root: NodeApi<T>;
visibleNodes: NodeApi<T>[];
Expand Down Expand Up @@ -91,7 +91,9 @@ export class TreeApi<T extends IdObj> {
const match =
this.props.searchMatch ??
((node, term) => {
const string = JSON.stringify(Object.values(node.data));
const string = JSON.stringify(
Object.values(node.data as { [k: string]: unknown })
);
return string.toLocaleLowerCase().includes(term.toLocaleLowerCase());
});
return (node: NodeApi<T>) => match(node, this.searchTerm);
Expand Down Expand Up @@ -190,13 +192,17 @@ export class TreeApi<T extends IdObj> {
index?: null | number;
} = {}
) {
const parentId =
opts.parentId === undefined
? utils.getInsertParentId(this)
: opts.parentId;
const index = opts.index ?? utils.getInsertIndex(this);
const type = opts.type ?? "leaf";
const data = await safeRun(this.props.onCreate, {
type: opts.type ?? "leaf",
parentId:
opts.parentId === undefined
? utils.getInsertParentId(this)
: opts.parentId,
index: opts.index ?? utils.getInsertIndex(this),
type,
parentId,
index,
parentNode: this.get(parentId),
});
if (data) {
this.focus(data);
Expand All @@ -211,9 +217,10 @@ export class TreeApi<T extends IdObj> {

async delete(node: string | IdObj | null | string[] | IdObj[]) {
if (!node) return;
const nodes = Array.isArray(node) ? node : [node];
const ids = nodes.map(identify);
await safeRun(this.props.onDelete, { ids });
const idents = Array.isArray(node) ? node : [node];
const ids = idents.map(identify);
const nodes = ids.map((id) => this.get(id)!).filter((n) => !!n);
await safeRun(this.props.onDelete, { nodes, ids });
}

edit(node: string | IdObj): Promise<EditResult> {
Expand All @@ -226,10 +233,14 @@ export class TreeApi<T extends IdObj> {
});
}

async submit(node: Identity, value: string) {
if (!node) return;
const id = identify(node);
await safeRun(this.props.onRename, { id, name: value });
async submit(identity: Identity, value: string) {
if (!identity) return;
const id = identify(identity);
await safeRun(this.props.onRename, {
id,
name: value,
node: this.get(id)!,
});
this.dispatch(edit(null));
this.resolveEdit({ cancelled: false, value });
setTimeout(() => this.onFocus()); // Return focus to element;
Expand Down
Loading

0 comments on commit fb73338

Please sign in to comment.