react-arborist 2.0.0-rc → 2.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,7 +28,8 @@ export interface TreeProps<T extends IdObj> {
28
28
  selectionFollowsFocus?: boolean;
29
29
  disableDrag?: string | boolean | BoolFunc<T>;
30
30
  disableDrop?: string | boolean | BoolFunc<T>;
31
- getChildren?: string | ((d: T) => T[]);
31
+ childrenAccessor?: string | ((d: T) => T[]);
32
+ idAccessor?: string | ((d: T) => string);
32
33
  onActivate?: (node: NodeApi<T>) => void;
33
34
  onSelect?: (nodes: NodeApi<T>[]) => void;
34
35
  onScroll?: (props: ListOnScrollProps) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-arborist",
3
- "version": "2.0.0-rc",
3
+ "version": "2.0.0-rc.1",
4
4
  "license": "MIT",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -29,6 +29,7 @@
29
29
  "react-dom": ">= 16.14"
30
30
  },
31
31
  "devDependencies": {
32
+ "@parcel/core": "^2.3.2",
32
33
  "@types/jest": "^27.4.1",
33
34
  "@types/react": "^18.0.0",
34
35
  "@types/react-window": "^1.8.5",
@@ -8,6 +8,11 @@ import { RowContainer } from "./row-container";
8
8
  let focusSearchTerm = "";
9
9
  let timeoutId: any = null;
10
10
 
11
+ /**
12
+ * All these keyboard shortcuts seem like they should be configurable.
13
+ * Each operation should be a given a name and separated from
14
+ * the event handler. Future clean up welcome.
15
+ */
11
16
  export function DefaultContainer() {
12
17
  useDataUpdates();
13
18
  const tree = useTreeApi();
@@ -37,6 +42,7 @@ export function DefaultContainer() {
37
42
  return;
38
43
  }
39
44
  if (e.key === "Backspace") {
45
+ if (!tree.props.onDelete) return;
40
46
  const ids = Array.from(tree.selectedIds);
41
47
  if (ids.length > 1) {
42
48
  let nextFocus = tree.mostRecentNode;
@@ -129,10 +135,12 @@ export function DefaultContainer() {
129
135
  return;
130
136
  }
131
137
  if (e.key === "a" && !e.metaKey) {
138
+ if (!tree.props.onCreate) return;
132
139
  tree.createLeaf();
133
140
  return;
134
141
  }
135
142
  if (e.key === "A" && !e.metaKey) {
143
+ if (!tree.props.onCreate) return;
136
144
  tree.createInternal();
137
145
  return;
138
146
  }
@@ -150,6 +158,7 @@ export function DefaultContainer() {
150
158
  return;
151
159
  }
152
160
  if (e.key === "Enter") {
161
+ if (!tree.props.onRename) return;
153
162
  setTimeout(() => {
154
163
  if (tree.focusedNode) tree.edit(tree.focusedNode);
155
164
  });
@@ -1,15 +1,50 @@
1
- import React from "react";
1
+ import React, { useEffect, useRef } from "react";
2
2
  import { NodeRendererProps } from "../types/renderers";
3
3
  import { IdObj } from "../types/utils";
4
4
 
5
- export function DefaultNode<T extends IdObj>({
6
- style,
7
- node,
8
- dragHandle,
9
- }: NodeRendererProps<T>) {
5
+ export function DefaultNode<T extends IdObj>(props: NodeRendererProps<T>) {
10
6
  return (
11
- <div style={style} ref={dragHandle}>
12
- ID: {node.data.id}
7
+ <div ref={props.dragHandle} style={props.style}>
8
+ <span
9
+ onClick={(e) => {
10
+ e.stopPropagation();
11
+ props.node.toggle();
12
+ }}
13
+ >
14
+ {props.node.isLeaf ? "🌳" : props.node.isOpen ? "🗁" : "🗀"}
15
+ </span>{" "}
16
+ {props.node.isEditing ? <Edit {...props} /> : <Show {...props} />}
13
17
  </div>
14
18
  );
15
19
  }
20
+
21
+ function Show<T extends IdObj>(props: NodeRendererProps<T>) {
22
+ return (
23
+ <>
24
+ {/* @ts-ignore */}
25
+ <span>{props.node.data.name}</span>
26
+ </>
27
+ );
28
+ }
29
+
30
+ function Edit<T extends IdObj>({ node }: NodeRendererProps<T>) {
31
+ const input = useRef<any>();
32
+
33
+ useEffect(() => {
34
+ input.current?.focus();
35
+ input.current?.select();
36
+ }, []);
37
+
38
+ return (
39
+ <input
40
+ ref={input}
41
+ // @ts-ignore
42
+ defaultValue={node.data.name}
43
+ onBlur={() => node.reset()}
44
+ onKeyDown={(e) => {
45
+ if (e.key === "Escape") node.reset();
46
+ if (e.key === "Enter") node.submit(input.current?.value || "");
47
+ }}
48
+ ></input>
49
+ );
50
+ }
@@ -15,7 +15,7 @@ export const ListOuterElement = forwardRef(function Outer(
15
15
  ref={ref}
16
16
  {...rest}
17
17
  onClick={(e) => {
18
- if (e.currentTarget === e.target) tree.selectNone();
18
+ if (e.currentTarget === e.target) tree.deselectAll();
19
19
  }}
20
20
  >
21
21
  <DropContainer />
@@ -67,7 +67,7 @@ export function TreeProvider<T extends IdObj>({
67
67
  if (api.props.selection) {
68
68
  api.select(api.props.selection);
69
69
  } else {
70
- api.selectNone();
70
+ api.deselectAll();
71
71
  }
72
72
  }, [api.props.selection]);
73
73
 
@@ -10,18 +10,19 @@ export function createRoot<T extends IdObj>(tree: TreeApi<T>): NodeApi<T> {
10
10
  level: number,
11
11
  parent: NodeApi<T> | null
12
12
  ) {
13
+ const id = tree.accessId(data);
13
14
  const node = new NodeApi<T>({
14
15
  tree,
15
16
  data,
16
17
  level,
17
18
  parent,
18
- id: data.id,
19
+ id,
19
20
  children: null,
20
21
  isDraggable: tree.isDraggable(data),
21
22
  isDroppable: tree.isDroppable(data),
22
23
  rowIndex: null,
23
24
  });
24
- const children = tree.getChildren(data);
25
+ const children = tree.accessChildren(data);
25
26
  if (children) {
26
27
  node.children = children.map((child: T) =>
27
28
  visitSelfAndChildren(child, level + 1, node)
@@ -38,21 +38,6 @@ export class NodeApi<T extends IdObj = IdObj> {
38
38
  this.rowIndex = params.rowIndex;
39
39
  }
40
40
 
41
- get next(): NodeApi<T> | null {
42
- if (this.rowIndex === null) return null;
43
- return this.tree.at(this.rowIndex + 1);
44
- }
45
-
46
- get prev(): NodeApi<T> | null {
47
- if (this.rowIndex === null) return null;
48
- return this.tree.at(this.rowIndex - 1);
49
- }
50
-
51
- get nextSibling(): NodeApi<T> | null {
52
- const i = this.childIndex;
53
- return this.parent?.children![i + 1] ?? null;
54
- }
55
-
56
41
  get isRoot() {
57
42
  return this.id === ROOT_ID;
58
43
  }
@@ -89,14 +74,6 @@ export class NodeApi<T extends IdObj = IdObj> {
89
74
  return this.tree.isFocused(this.id);
90
75
  }
91
76
 
92
- get childIndex() {
93
- if (this.parent && this.parent.children) {
94
- return this.parent.children.findIndex((child) => child.id === this.id);
95
- } else {
96
- return -1;
97
- }
98
- }
99
-
100
77
  get isDragging() {
101
78
  return this.tree.isDragging(this.id);
102
79
  }
@@ -118,6 +95,29 @@ export class NodeApi<T extends IdObj = IdObj> {
118
95
  };
119
96
  }
120
97
 
98
+ get childIndex() {
99
+ if (this.parent && this.parent.children) {
100
+ return this.parent.children.findIndex((child) => child.id === this.id);
101
+ } else {
102
+ return -1;
103
+ }
104
+ }
105
+
106
+ get next(): NodeApi<T> | null {
107
+ if (this.rowIndex === null) return null;
108
+ return this.tree.at(this.rowIndex + 1);
109
+ }
110
+
111
+ get prev(): NodeApi<T> | null {
112
+ if (this.rowIndex === null) return null;
113
+ return this.tree.at(this.rowIndex - 1);
114
+ }
115
+
116
+ get nextSibling(): NodeApi<T> | null {
117
+ const i = this.childIndex;
118
+ return this.parent?.children![i + 1] ?? null;
119
+ }
120
+
121
121
  select() {
122
122
  this.tree.select(this);
123
123
  }
@@ -2,12 +2,7 @@ import { EditResult } from "../types/handlers";
2
2
  import { Identity, IdObj } from "../types/utils";
3
3
  import { TreeProps } from "../types/tree-props";
4
4
  import { MutableRefObject } from "react";
5
- import {
6
- Align,
7
- FixedSizeList,
8
- ListOnItemsRenderedProps,
9
- ListOnScrollProps,
10
- } from "react-window";
5
+ import { Align, FixedSizeList, ListOnItemsRenderedProps } from "react-window";
11
6
  import * as utils from "../utils";
12
7
  import { DefaultCursor } from "../components/default-cursor";
13
8
  import { DefaultRow } from "../components/default-row";
@@ -102,11 +97,21 @@ export class TreeApi<T extends IdObj> {
102
97
  return (node: NodeApi<T>) => match(node, this.searchTerm);
103
98
  }
104
99
 
105
- getChildren(data: T) {
106
- const get = this.props.getChildren || "children";
100
+ accessChildren(data: T) {
101
+ const get = this.props.childrenAccessor || "children";
107
102
  return utils.access<T[] | undefined>(data, get) ?? null;
108
103
  }
109
104
 
105
+ accessId(data: T) {
106
+ const get = this.props.idAccessor || "id";
107
+ const id = utils.access<string>(data, get);
108
+ if (!id)
109
+ throw new Error(
110
+ "Data must contain an 'id' property or props.idAccessor must return a string"
111
+ );
112
+ return id;
113
+ }
114
+
110
115
  /* Node Access */
111
116
 
112
117
  get firstNode() {
@@ -348,7 +353,7 @@ export class TreeApi<T extends IdObj> {
348
353
  safeRun(this.props.onSelect, this.selectedNodes);
349
354
  }
350
355
 
351
- selectNone() {
356
+ deselectAll() {
352
357
  this.dispatch(selection.clear());
353
358
  this.dispatch(selection.anchor(null));
354
359
  this.dispatch(selection.mostRecent(null));
@@ -38,7 +38,8 @@ export interface TreeProps<T extends IdObj> {
38
38
  selectionFollowsFocus?: boolean;
39
39
  disableDrag?: string | boolean | BoolFunc<T>;
40
40
  disableDrop?: string | boolean | BoolFunc<T>;
41
- getChildren?: string | ((d: T) => T[]);
41
+ childrenAccessor?: string | ((d: T) => T[]);
42
+ idAccessor?: string | ((d: T) => string);
42
43
 
43
44
  /* Event Handlers */
44
45
  onActivate?: (node: NodeApi<T>) => void;
package/tsconfig.json CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  /* Language and Environment */
7
7
  "target": "ES5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
8
- "jsx": "preserve", /* Specify what JSX code is generated. */
8
+ "jsx": "react-jsx", /* Specify what JSX code is generated. */
9
9
 
10
10
  /* Modules */
11
11
  "module": "CommonJS", /* Specify what module code is generated. */
@@ -1,4 +0,0 @@
1
- import { NodeApi } from "../interfaces/node-api";
2
- import { IdObj } from "../types/utils";
3
- export declare function flattenTree<T extends IdObj>(root: NodeApi<T>): NodeApi<T>[];
4
- export declare function filterTree<T extends IdObj>(root: NodeApi<T>, isMatch: (n: NodeApi<T>) => boolean): NodeApi<T>[];
@@ -1,24 +0,0 @@
1
- declare type Data = {
2
- id: string;
3
- name: string;
4
- children?: Data[];
5
- };
6
- export declare function useUncontrolledTree(initialData: Data[]): readonly [Data[], {
7
- move: (args: {
8
- dragIds: string[];
9
- parentId: string;
10
- index: number;
11
- }) => void;
12
- rename: (args: {
13
- id: string;
14
- name: string;
15
- }) => void;
16
- create: ({ parentId, index }: {
17
- parentId: string;
18
- index: number;
19
- }) => Data;
20
- drop: (args: {
21
- id: string;
22
- }) => void;
23
- }];
24
- export {};
@@ -1,3 +0,0 @@
1
- import { TreeProps } from "../types/tree-props";
2
- import { IdObj } from "../types/utils";
3
- export declare function validateProps<T extends IdObj>(props: TreeProps<T>): TreeProps<T>;
@@ -1,8 +0,0 @@
1
- import { TreeProps } from "../types/tree-props";
2
- import { IdObj } from "../types/utils";
3
-
4
- export function validateProps<T extends IdObj>(
5
- props: TreeProps<T>
6
- ): TreeProps<T> {
7
- return props;
8
- }