react-arborist 0.2.0 → 1.0.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-arborist",
3
- "version": "0.2.0",
3
+ "version": "1.0.0",
4
4
  "license": "MIT",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -31,6 +31,5 @@
31
31
  "react": "^17.0.2",
32
32
  "react-dom": "^17.0.2",
33
33
  "typescript": "^4.6.2"
34
- },
35
- "stableVersion": "0.2.0-beta.0"
34
+ }
36
35
  }
@@ -91,13 +91,13 @@ const PreviewNode = memo(function PreviewNode(props: {
91
91
  isDragging: false,
92
92
  isEditing: false,
93
93
  isSelected: false,
94
- isFirstOfSelected: false,
95
- isLastOfSelected: false,
94
+ isSelectedStart: false,
95
+ isSelectedEnd: false,
96
96
  isHoveringOverChild: false,
97
97
  isOpen: node.isOpen,
98
98
  }}
99
99
  handlers={{
100
- edit: () => {},
100
+ edit: () => Promise.resolve({ cancelled: true }),
101
101
  select: () => {},
102
102
  toggle: () => {},
103
103
  submit: () => {},
@@ -37,8 +37,8 @@ export const Row = React.memo(function Row({ index, style }: Props) {
37
37
  return {
38
38
  isEditing,
39
39
  isDragging,
40
- isFirstOfSelected: isSelected && !prevSelected,
41
- isLastOfSelected: isSelected && !nextSelected,
40
+ isSelectedStart: isSelected && !prevSelected,
41
+ isSelectedEnd: isSelected && !nextSelected,
42
42
  isSelected,
43
43
  isHoveringOverChild,
44
44
  isOpen,
@@ -54,9 +54,6 @@ export const Row = React.memo(function Row({ index, style }: Props) {
54
54
  isDragging,
55
55
  isOverFolder,
56
56
  ]);
57
- if (isSelected) {
58
- console.log({id: node.id, state})
59
- }
60
57
 
61
58
  const ref = useCallback(
62
59
  (n: HTMLDivElement | null) => {
@@ -76,9 +73,12 @@ export const Row = React.memo(function Row({ index, style }: Props) {
76
73
 
77
74
  const handlers = useMemo(() => {
78
75
  return {
79
- select: (e: React.MouseEvent, selectOnClick: boolean = true) => {
76
+ select: (
77
+ e: React.MouseEvent,
78
+ args: { selectOnClick: boolean } = { selectOnClick: true }
79
+ ) => {
80
80
  if (node.rowIndex === null) return;
81
- if (selectOnClick || e.metaKey || e.shiftKey) {
81
+ if (args.selectOnClick || e.metaKey || e.shiftKey) {
82
82
  tree.api.select(node.rowIndex, e.metaKey, e.shiftKey);
83
83
  } else {
84
84
  tree.api.select(null, false, false);
@@ -88,16 +88,11 @@ export const Row = React.memo(function Row({ index, style }: Props) {
88
88
  e.stopPropagation();
89
89
  tree.onToggle(node.id, !node.isOpen);
90
90
  },
91
- edit: () => {
92
- tree.api.edit(node.id);
93
- },
91
+ edit: () => tree.api.edit(node.id),
94
92
  submit: (name: string) => {
95
- if (name.trim()) tree.onEdit(node.id, name);
96
- tree.api.edit(null);
97
- },
98
- reset: () => {
99
- tree.api.edit(null);
93
+ name.trim() ? tree.api.submit(node.id, name) : tree.api.reset(node.id);
100
94
  },
95
+ reset: () => tree.api.reset(node.id),
101
96
  };
102
97
  }, [tree, node]);
103
98
 
package/src/tree-api.ts CHANGED
@@ -4,9 +4,12 @@ import { FixedSizeList } from "react-window";
4
4
  import { flattenTree } from "./data/flatten-tree";
5
5
  import { Cursor } from "./dnd/compute-drop";
6
6
  import { Action, actions } from "./reducer";
7
- import { Node, StateContext, TreeProviderProps } from "./types";
7
+ import { Node, StateContext, TreeProviderProps, EditResult } from "./types";
8
+ import ReactDOM from "react-dom";
8
9
 
9
10
  export class TreeApi<T = unknown> {
11
+ private edits = new Map<string, (args: EditResult) => void>();
12
+
10
13
  constructor(
11
14
  public dispatch: Dispatch<Action>,
12
15
  public state: StateContext,
@@ -36,14 +39,42 @@ export class TreeApi<T = unknown> {
36
39
  return this.state.selection.ids;
37
40
  }
38
41
 
39
- edit(id: string | number | null) {
40
- this.dispatch(actions.edit(id ? id.toString() : null));
42
+ edit(id: string | number): Promise<EditResult> {
43
+ const sid = id.toString();
44
+ this.resolveEdit(sid, { cancelled: true });
45
+ this.scrollToId(sid);
46
+ this.dispatch(actions.edit(sid));
47
+ return new Promise((resolve) => this.edits.set(sid, resolve));
48
+ }
49
+
50
+ submit(id: string | number, value: string) {
51
+ const sid = id.toString();
52
+ this.props.onEdit(sid, value);
53
+ this.dispatch(actions.edit(null));
54
+ this.resolveEdit(sid, { cancelled: false, value });
55
+ }
56
+
57
+ reset(id: string | number) {
58
+ const sid = id.toString();
59
+ this.dispatch(actions.edit(null));
60
+ this.resolveEdit(sid, { cancelled: true });
41
61
  }
42
62
 
43
- select(index: number | null, meta: boolean, shift: boolean) {
63
+ private resolveEdit(id: string, value: EditResult) {
64
+ const resolve = this.edits.get(id.toString());
65
+ if (resolve) resolve(value);
66
+ this.edits.delete(id);
67
+ }
68
+
69
+ select(index: number | null, meta = false, shift = false) {
44
70
  this.dispatch(actions.select(index, meta, shift));
45
71
  }
46
72
 
73
+ selectById(id: string | number, meta = false, shift = false) {
74
+ const index = this.idToIndex[id];
75
+ this.select(index, meta, shift);
76
+ }
77
+
47
78
  selectUpwards(shiftKey: boolean) {
48
79
  this.dispatch(actions.stepUp(shiftKey, this.visibleIds));
49
80
  }
@@ -67,11 +98,7 @@ export class TreeApi<T = unknown> {
67
98
  this.list.scrollToItem(index, "start");
68
99
  } else {
69
100
  this.openParents(id);
70
- // This appears to be synchronous
71
- // But I've only tested it in the console and
72
- // not in an event handler which will be batched...
73
- // We may need to wrap this in a timeout or trigger an effect somehow
74
- setTimeout(() => {
101
+ ReactDOM.flushSync(() => {
75
102
  const index = this.idToIndex[id];
76
103
  if (index) {
77
104
  this.list?.scrollToItem(index, "start");
package/src/types.ts CHANGED
@@ -53,15 +53,15 @@ export type NodeState = {
53
53
  isSelected: boolean;
54
54
  isHoveringOverChild: boolean;
55
55
  isDragging: boolean;
56
- isFirstOfSelected: boolean;
57
- isLastOfSelected: boolean;
56
+ isSelectedStart: boolean;
57
+ isSelectedEnd: boolean;
58
58
  isEditing: boolean;
59
59
  };
60
60
 
61
61
  export type NodeHandlers = {
62
62
  toggle: MouseEventHandler;
63
- select: (e: MouseEvent, selectOnClick?: boolean) => void;
64
- edit: () => void;
63
+ select: (e: MouseEvent, args: { selectOnClick: boolean }) => void;
64
+ edit: () => Promise<EditResult>;
65
65
  submit: (name: string) => void;
66
66
  reset: () => void;
67
67
  };
@@ -145,3 +145,7 @@ export type StaticContext<T> = TreeProviderProps<T> & {
145
145
  api: TreeApi<T>;
146
146
  list: MutableRefObject<FixedSizeList | undefined>;
147
147
  };
148
+
149
+ export type EditResult =
150
+ | { cancelled: true }
151
+ | { cancelled: false; value: string };