react-arborist 1.2.0 → 2.0.0-rc

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.
Files changed (120) hide show
  1. package/dist/components/{drop-cursor.d.ts → cursor.d.ts} +0 -0
  2. package/dist/components/default-container.d.ts +2 -0
  3. package/dist/components/default-cursor.d.ts +3 -0
  4. package/dist/components/default-drag-preview.d.ts +3 -0
  5. package/dist/components/default-node.d.ts +4 -0
  6. package/dist/components/default-row.d.ts +4 -0
  7. package/dist/components/drag-preview-container.d.ts +2 -0
  8. package/dist/components/list-inner-element.d.ts +2 -0
  9. package/dist/components/provider.d.ts +11 -0
  10. package/dist/components/row-container.d.ts +8 -0
  11. package/dist/components/tree-container.d.ts +2 -0
  12. package/dist/components/tree.d.ts +5 -4
  13. package/dist/context.d.ts +20 -2
  14. package/dist/data/create-index.d.ts +5 -0
  15. package/dist/data/create-list.d.ts +4 -0
  16. package/dist/data/create-root.d.ts +5 -0
  17. package/dist/data/flatten-tree.d.ts +4 -2
  18. package/dist/data/simple-tree.d.ts +43 -0
  19. package/dist/dnd/compute-drop.d.ts +4 -4
  20. package/dist/dnd/drag-hook.d.ts +3 -4
  21. package/dist/dnd/drop-hook.d.ts +2 -3
  22. package/dist/hooks/use-fresh-node.d.ts +2 -0
  23. package/dist/hooks/use-simple-tree.d.ts +13 -0
  24. package/dist/hooks/use-uncontrolled-tree.d.ts +24 -0
  25. package/dist/hooks/use-validated-props.d.ts +3 -0
  26. package/dist/index.d.ts +8 -4
  27. package/dist/index.js +1900 -971
  28. package/dist/index.js.map +1 -1
  29. package/dist/interfaces/node-api.d.ts +67 -0
  30. package/dist/interfaces/tree-api.d.ts +112 -0
  31. package/dist/module.js +1886 -976
  32. package/dist/module.js.map +1 -1
  33. package/dist/state/dnd-slice.d.ts +20 -0
  34. package/dist/state/drag-slice.d.ts +7 -0
  35. package/dist/state/edit-slice.d.ts +8 -0
  36. package/dist/state/focus-slice.d.ts +12 -0
  37. package/dist/state/initial.d.ts +3 -0
  38. package/dist/state/open-slice.d.ts +30 -0
  39. package/dist/state/root-reducer.d.ts +13 -0
  40. package/dist/state/selection-slice.d.ts +36 -0
  41. package/dist/types/dnd.d.ts +9 -0
  42. package/dist/types/handlers.d.ts +24 -0
  43. package/dist/types/renderers.d.ts +30 -0
  44. package/dist/types/state.d.ts +2 -0
  45. package/dist/types/tree-props.d.ts +43 -0
  46. package/dist/types/utils.d.ts +21 -0
  47. package/dist/utils/props.d.ts +3 -0
  48. package/dist/utils.d.ts +15 -6
  49. package/package.json +10 -7
  50. package/src/components/cursor.tsx +15 -0
  51. package/src/components/default-container.tsx +229 -0
  52. package/src/components/{default-drop-cursor.tsx → default-cursor.tsx} +9 -8
  53. package/src/components/{preview.tsx → default-drag-preview.tsx} +25 -41
  54. package/src/components/default-node.tsx +15 -0
  55. package/src/components/default-row.tsx +21 -0
  56. package/src/components/drag-preview-container.tsx +26 -0
  57. package/src/components/list-inner-element.tsx +22 -0
  58. package/src/components/list-outer-element.tsx +26 -15
  59. package/src/components/provider.tsx +97 -0
  60. package/src/components/row-container.tsx +82 -0
  61. package/src/components/tree-container.tsx +13 -0
  62. package/src/components/tree.tsx +16 -44
  63. package/src/context.ts +36 -0
  64. package/src/data/create-index.ts +9 -0
  65. package/src/data/create-list.ts +56 -0
  66. package/src/data/create-root.ts +53 -0
  67. package/src/data/simple-tree.ts +103 -0
  68. package/src/dnd/compute-drop.ts +16 -16
  69. package/src/dnd/drag-hook.ts +25 -19
  70. package/src/dnd/drop-hook.ts +31 -17
  71. package/src/dnd/outer-drop-hook.ts +1 -1
  72. package/src/hooks/use-fresh-node.ts +16 -0
  73. package/src/hooks/use-simple-tree.ts +55 -0
  74. package/src/hooks/use-validated-props.ts +35 -0
  75. package/src/index.ts +9 -19
  76. package/src/interfaces/node-api.ts +187 -0
  77. package/src/interfaces/tree-api.ts +552 -0
  78. package/src/state/dnd-slice.ts +36 -0
  79. package/src/state/drag-slice.ts +31 -0
  80. package/src/state/edit-slice.ts +19 -0
  81. package/src/state/focus-slice.ts +28 -0
  82. package/src/state/initial.ts +14 -0
  83. package/src/state/open-slice.ts +53 -0
  84. package/src/state/root-reducer.ts +21 -0
  85. package/src/state/selection-slice.ts +75 -0
  86. package/src/types/dnd.ts +10 -0
  87. package/src/types/handlers.ts +24 -0
  88. package/src/types/renderers.ts +34 -0
  89. package/src/types/state.ts +3 -0
  90. package/src/types/tree-props.ts +63 -0
  91. package/src/types/utils.ts +26 -0
  92. package/src/utils/props.ts +8 -0
  93. package/src/utils.ts +125 -11
  94. package/README.md +0 -221
  95. package/dist/components/default-drop-cursor.d.ts +0 -3
  96. package/dist/components/list.d.ts +0 -4
  97. package/dist/components/preview.d.ts +0 -2
  98. package/dist/components/row.d.ts +0 -8
  99. package/dist/data/enrich-tree.d.ts +0 -2
  100. package/dist/provider.d.ts +0 -3
  101. package/dist/reducer.d.ts +0 -46
  102. package/dist/selection/range.d.ts +0 -13
  103. package/dist/selection/selection-hook.d.ts +0 -4
  104. package/dist/selection/selection.d.ts +0 -33
  105. package/dist/tree-api.d.ts +0 -50
  106. package/dist/types.d.ts +0 -122
  107. package/src/components/drop-cursor.tsx +0 -12
  108. package/src/components/list.tsx +0 -25
  109. package/src/components/row.tsx +0 -112
  110. package/src/context.tsx +0 -13
  111. package/src/data/enrich-tree.ts +0 -74
  112. package/src/data/flatten-tree.ts +0 -17
  113. package/src/provider.tsx +0 -41
  114. package/src/reducer.ts +0 -161
  115. package/src/selection/range.ts +0 -41
  116. package/src/selection/selection-hook.ts +0 -25
  117. package/src/selection/selection.test.ts +0 -111
  118. package/src/selection/selection.ts +0 -186
  119. package/src/tree-api.ts +0 -230
  120. package/src/types.ts +0 -148
@@ -1,7 +1,8 @@
1
1
  import React, { CSSProperties, memo } from "react";
2
- import { useDragLayer, XYCoord } from "react-dnd";
2
+ import { XYCoord } from "react-dnd";
3
3
  import { useTreeApi } from "../context";
4
- import { DragItem, IdObj } from "../types";
4
+ import { DragPreviewProps } from "../types/renderers";
5
+ import { IdObj } from "../types/utils";
5
6
 
6
7
  const layerStyles: CSSProperties = {
7
8
  position: "fixed",
@@ -25,20 +26,19 @@ const getCountStyle = (offset: XYCoord | null) => {
25
26
  return { transform: `translate(${x + 10}px, ${y + 10}px)` };
26
27
  };
27
28
 
28
- export function Preview() {
29
- const { offset, mouse, item, isDragging } = useDragLayer((m) => ({
30
- offset: m.getSourceClientOffset(),
31
- mouse: m.getClientOffset(),
32
- item: m.getItem(),
33
- isDragging: m.isDragging(),
34
- }));
35
-
29
+ export function DefaultDragPreview({
30
+ offset,
31
+ mouse,
32
+ id,
33
+ dragIds,
34
+ isDragging,
35
+ }: DragPreviewProps) {
36
36
  return (
37
37
  <Overlay isDragging={isDragging}>
38
38
  <Position offset={offset}>
39
- <PreviewNode item={item} />
39
+ <PreviewNode id={id} dragIds={dragIds} />
40
40
  </Position>
41
- <Count mouse={mouse} item={item} />
41
+ <Count mouse={mouse} count={dragIds.length} />
42
42
  </Overlay>
43
43
  );
44
44
  }
@@ -59,50 +59,34 @@ function Position(props: { children: JSX.Element; offset: XYCoord | null }) {
59
59
  );
60
60
  }
61
61
 
62
- function Count(props: { item: DragItem; mouse: XYCoord | null }) {
63
- const { item, mouse } = props;
64
- if (item?.dragIds?.length > 1)
62
+ function Count(props: { count: number; mouse: XYCoord | null }) {
63
+ const { count, mouse } = props;
64
+ if (count > 1)
65
65
  return (
66
66
  <div className="selected-count" style={getCountStyle(mouse)}>
67
- {item.dragIds.length}
67
+ {count}
68
68
  </div>
69
69
  );
70
70
  else return null;
71
71
  }
72
72
 
73
73
  const PreviewNode = memo(function PreviewNode<T extends IdObj>(props: {
74
- item: DragItem | null;
74
+ id: string | null;
75
+ dragIds: string[];
75
76
  }) {
76
77
  const tree = useTreeApi<T>();
77
- if (!props.item) return null;
78
- const node = tree.getNode(props.item.id);
78
+ const node = tree.get(props.id);
79
79
  if (!node) return null;
80
80
  return (
81
- <tree.renderer
81
+ <tree.renderNode
82
82
  preview
83
- innerRef={() => {}}
84
- data={node.model}
85
- styles={{
86
- row: {},
87
- indent: { paddingLeft: node.level * tree.indent },
83
+ node={node}
84
+ style={{
85
+ paddingLeft: node.level * tree.indent,
86
+ opacity: 0.2,
87
+ background: "transparent",
88
88
  }}
89
89
  tree={tree}
90
- state={{
91
- isDragging: false,
92
- isEditing: false,
93
- isSelected: false,
94
- isSelectedStart: false,
95
- isSelectedEnd: false,
96
- isHoveringOverChild: false,
97
- isOpen: node.isOpen,
98
- }}
99
- handlers={{
100
- edit: () => Promise.resolve({ cancelled: true }),
101
- select: () => {},
102
- toggle: () => {},
103
- submit: () => {},
104
- reset: () => {},
105
- }}
106
90
  />
107
91
  );
108
92
  });
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import { NodeRendererProps } from "../types/renderers";
3
+ import { IdObj } from "../types/utils";
4
+
5
+ export function DefaultNode<T extends IdObj>({
6
+ style,
7
+ node,
8
+ dragHandle,
9
+ }: NodeRendererProps<T>) {
10
+ return (
11
+ <div style={style} ref={dragHandle}>
12
+ ID: {node.data.id}
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { RowRendererProps } from "../types/renderers";
3
+ import { IdObj } from "../types/utils";
4
+
5
+ export function DefaultRow<T extends IdObj>({
6
+ node,
7
+ attrs,
8
+ innerRef,
9
+ children,
10
+ }: RowRendererProps<T>) {
11
+ return (
12
+ <div
13
+ {...attrs}
14
+ ref={innerRef}
15
+ onFocus={(e) => e.stopPropagation()}
16
+ onClick={node.handleClick}
17
+ >
18
+ {children}
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,26 @@
1
+ import { useDragLayer } from "react-dnd";
2
+ import { useDndContext, useTreeApi } from "../context";
3
+ import { DefaultDragPreview } from "./default-drag-preview";
4
+
5
+ export function DragPreviewContainer() {
6
+ const tree = useTreeApi();
7
+ const { offset, mouse, item, isDragging } = useDragLayer((m) => {
8
+ return {
9
+ offset: m.getSourceClientOffset(),
10
+ mouse: m.getClientOffset(),
11
+ item: m.getItem(),
12
+ isDragging: m.isDragging(),
13
+ };
14
+ });
15
+
16
+ const DragPreview = tree.props.renderDragPreview || DefaultDragPreview;
17
+ return (
18
+ <DragPreview
19
+ offset={offset}
20
+ mouse={mouse}
21
+ id={item?.id || null}
22
+ dragIds={item?.dragIds || []}
23
+ isDragging={isDragging}
24
+ />
25
+ );
26
+ }
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import { forwardRef } from "react";
3
+ import { useTreeApi } from "../context";
4
+
5
+ export const ListInnerElement = forwardRef<any, any>(function InnerElement(
6
+ { style, ...rest },
7
+ ref
8
+ ) {
9
+ const tree = useTreeApi();
10
+ const paddingTop = tree.props.padding ?? tree.props.paddingTop ?? 0;
11
+ const paddingBottom = tree.props.padding ?? tree.props.paddingBottom ?? 0;
12
+ return (
13
+ <div
14
+ ref={ref}
15
+ style={{
16
+ ...style,
17
+ height: `${parseFloat(style.height) + paddingTop + paddingBottom}px`,
18
+ }}
19
+ {...rest}
20
+ />
21
+ );
22
+ });
@@ -1,6 +1,7 @@
1
1
  import { forwardRef } from "react";
2
2
  import { useTreeApi } from "../context";
3
- import { DropCursor } from "./drop-cursor";
3
+ import { treeBlur } from "../state/focus-slice";
4
+ import { DropCursor } from "./cursor";
4
5
 
5
6
  export const ListOuterElement = forwardRef(function Outer(
6
7
  props: React.HTMLProps<HTMLDivElement>,
@@ -13,22 +14,32 @@ export const ListOuterElement = forwardRef(function Outer(
13
14
  // @ts-ignore
14
15
  ref={ref}
15
16
  {...rest}
16
- onClick={tree.onClick}
17
- onContextMenu={tree.onContextMenu}
17
+ onClick={(e) => {
18
+ if (e.currentTarget === e.target) tree.selectNone();
19
+ }}
18
20
  >
19
- <div
20
- style={{
21
- height: tree.visibleNodes.length * tree.rowHeight,
22
- width: "100%",
23
- overflow: "hidden",
24
- position: "absolute",
25
- left: "0",
26
- right: "0",
27
- }}
28
- >
29
- <DropCursor />
30
- </div>
21
+ <DropContainer />
31
22
  {children}
32
23
  </div>
33
24
  );
34
25
  });
26
+
27
+ const DropContainer = () => {
28
+ const tree = useTreeApi();
29
+ return (
30
+ <div
31
+ style={{
32
+ height: tree.visibleNodes.length * tree.rowHeight,
33
+ width: "100%",
34
+ position: "absolute",
35
+ left: "0",
36
+ right: "0",
37
+ }}
38
+ onClick={(e) => {
39
+ console.log(e.currentTarget, e.target);
40
+ }}
41
+ >
42
+ <DropCursor />
43
+ </div>
44
+ );
45
+ };
@@ -0,0 +1,97 @@
1
+ import {
2
+ ReactNode,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useMemo,
6
+ useRef,
7
+ } from "react";
8
+ import { useSyncExternalStore } from "use-sync-external-store/shim";
9
+ import { FixedSizeList } from "react-window";
10
+ import {
11
+ DataUpdatesContext,
12
+ DndContext,
13
+ NodesContext,
14
+ TreeApiContext,
15
+ } from "../context";
16
+ import { TreeApi } from "../interfaces/tree-api";
17
+ import { IdObj } from "../types/utils";
18
+ import { initialState } from "../state/initial";
19
+ import { rootReducer, RootState } from "../state/root-reducer";
20
+ import { HTML5Backend } from "react-dnd-html5-backend";
21
+ import { DndProvider } from "react-dnd";
22
+ import { TreeProps } from "../types/tree-props";
23
+ import { createStore, Store } from "redux";
24
+ import { actions as visibility } from "../state/open-slice";
25
+
26
+ type Props<T extends IdObj> = {
27
+ treeProps: TreeProps<T>;
28
+ imperativeHandle: React.Ref<TreeApi<T> | undefined>;
29
+ children: ReactNode;
30
+ };
31
+
32
+ const SERVER_STATE = initialState();
33
+
34
+ export function TreeProvider<T extends IdObj>({
35
+ treeProps,
36
+ imperativeHandle,
37
+ children,
38
+ }: Props<T>) {
39
+ const list = useRef<FixedSizeList | null>(null);
40
+ const listEl = useRef<HTMLDivElement | null>(null);
41
+ const store = useRef<Store>(
42
+ createStore(rootReducer, initialState(treeProps))
43
+ );
44
+ const state = useSyncExternalStore<RootState>(
45
+ store.current.subscribe,
46
+ store.current.getState,
47
+ () => SERVER_STATE
48
+ );
49
+
50
+ /* The tree api object is stable. */
51
+ const api = useMemo(() => {
52
+ return new TreeApi<T>(store.current, treeProps, list, listEl);
53
+ }, []);
54
+
55
+ /* Make sure the tree instance stays in sync */
56
+ const updateCount = useRef(0);
57
+ useMemo(() => {
58
+ updateCount.current += 1;
59
+ api.update(treeProps);
60
+ }, [...Object.values(treeProps), state.nodes.open]);
61
+
62
+ /* Expose the tree api */
63
+ useImperativeHandle(imperativeHandle, () => api);
64
+
65
+ /* Change selection based on props */
66
+ useEffect(() => {
67
+ if (api.props.selection) {
68
+ api.select(api.props.selection);
69
+ } else {
70
+ api.selectNone();
71
+ }
72
+ }, [api.props.selection]);
73
+
74
+ /* Clear visability for filtered nodes */
75
+ useEffect(() => {
76
+ if (!api.props.searchTerm) {
77
+ store.current.dispatch(visibility.clear(true));
78
+ }
79
+ }, [api.props.searchTerm]);
80
+
81
+ return (
82
+ <TreeApiContext.Provider value={api}>
83
+ <DataUpdatesContext.Provider value={updateCount.current}>
84
+ <NodesContext.Provider value={state.nodes}>
85
+ <DndContext.Provider value={state.dnd}>
86
+ <DndProvider
87
+ backend={HTML5Backend}
88
+ options={{ rootElement: api.props.dndRootElement || undefined }}
89
+ >
90
+ {children}
91
+ </DndProvider>
92
+ </DndContext.Provider>
93
+ </NodesContext.Provider>
94
+ </DataUpdatesContext.Provider>
95
+ </TreeApiContext.Provider>
96
+ );
97
+ }
@@ -0,0 +1,82 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef } from "react";
2
+ import { useDataUpdates, useNodesContext, useTreeApi } from "../context";
3
+ import { useDragHook } from "../dnd/drag-hook";
4
+ import { useDropHook } from "../dnd/drop-hook";
5
+ import { IdObj } from "../types/utils";
6
+ import { useFreshNode } from "../hooks/use-fresh-node";
7
+
8
+ type Props = {
9
+ style: React.CSSProperties;
10
+ index: number;
11
+ };
12
+
13
+ export const RowContainer = React.memo(function RowContainer<T extends IdObj>({
14
+ index,
15
+ style,
16
+ }: Props) {
17
+ /* When will the <Row> will re-render.
18
+ *
19
+ * The row component is memo'd so it will only render
20
+ * when a new instance of the NodeApi class is passed
21
+ * to it.
22
+ *
23
+ * The TreeApi instance is stable. It does not
24
+ * change when the internal state changes.
25
+ *
26
+ * The TreeApi has all the references to the nodes.
27
+ * We need to clone the nodes when their state
28
+ * changes. The node class contains no state itself,
29
+ * It always checks the tree for state. The tree's
30
+ * state will always be up to date.
31
+ */
32
+
33
+ useDataUpdates(); // Re-render when tree props or visability changes
34
+ const _ = useNodesContext(); // So that we re-render appropriately
35
+ const tree = useTreeApi<T>(); // Tree already has the fresh state
36
+ const node = useFreshNode<T>(index);
37
+
38
+ const el = useRef<HTMLDivElement | null>(null);
39
+ const dragRef = useDragHook<T>(node);
40
+ const dropRef = useDropHook(el, node);
41
+ const innerRef = useCallback(
42
+ (n: any) => {
43
+ el.current = n;
44
+ dropRef(n);
45
+ },
46
+ [dropRef]
47
+ );
48
+
49
+ const indent = tree.indent * node.level;
50
+ const nodeStyle = useMemo(() => ({ paddingLeft: indent }), [indent]);
51
+ const rowStyle = useMemo(
52
+ () => ({
53
+ ...style,
54
+ top:
55
+ parseFloat(style.top as string) +
56
+ (tree.props.padding ?? tree.props.paddingTop ?? 0),
57
+ }),
58
+ [style, tree.props.padding, tree.props.paddingTop]
59
+ );
60
+ const rowAttrs: React.HTMLAttributes<any> = {
61
+ role: "treeitem",
62
+ "aria-level": node.level,
63
+ "aria-selected": node.isSelected,
64
+ style: rowStyle,
65
+ tabIndex: -1,
66
+ };
67
+
68
+ useEffect(() => {
69
+ if (!node.isEditing && node.isFocused) {
70
+ el.current?.focus();
71
+ }
72
+ }, [node.isEditing, node.isFocused, el.current]);
73
+
74
+ const Node = tree.renderNode;
75
+ const Row = tree.renderRow;
76
+
77
+ return (
78
+ <Row node={node} innerRef={innerRef} attrs={rowAttrs}>
79
+ <Node node={node} tree={tree} style={nodeStyle} dragHandle={dragRef} />
80
+ </Row>
81
+ );
82
+ });
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import { useTreeApi } from "../context";
3
+ import { DefaultContainer } from "./default-container";
4
+
5
+ export function TreeContainer() {
6
+ const tree = useTreeApi();
7
+ const Container = tree.props.renderContainer || DefaultContainer;
8
+ return (
9
+ <>
10
+ <Container />
11
+ </>
12
+ );
13
+ }
@@ -1,52 +1,24 @@
1
- import { forwardRef, ReactElement, useMemo, useRef } from "react";
2
- import { DndProvider } from "react-dnd";
3
- import { HTML5Backend } from "react-dnd-html5-backend";
4
- import { enrichTree } from "../data/enrich-tree";
5
- import { TreeViewProvider } from "../provider";
6
- import { TreeApi } from "../tree-api";
7
- import { IdObj, Node, TreeProps } from "../types";
8
- import { noop } from "../utils";
9
- import { Preview } from "./preview";
1
+ import { forwardRef } from "react";
2
+ import { TreeProvider } from "./provider";
3
+ import { TreeApi } from "../interfaces/tree-api";
10
4
  import { OuterDrop } from "./outer-drop";
11
- import { List } from "./list";
5
+ import { TreeContainer } from "./tree-container";
6
+ import { DragPreviewContainer } from "./drag-preview-container";
7
+ import { TreeProps } from "../types/tree-props";
8
+ import { IdObj } from "../types/utils";
9
+ import { useValidatedProps } from "../hooks/use-validated-props";
12
10
 
13
11
  export const Tree = forwardRef(function Tree<T extends IdObj>(
14
12
  props: TreeProps<T>,
15
- ref: React.Ref<TreeApi<T>>
13
+ ref: React.Ref<TreeApi<T> | undefined>
16
14
  ) {
17
- const root = useMemo<Node<T>>(
18
- () =>
19
- enrichTree<T>(
20
- props.data,
21
- props.hideRoot,
22
- props.getChildren,
23
- props.isOpen,
24
- props.disableDrag,
25
- props.disableDrop,
26
- props.openByDefault
27
- ),
28
- [
29
- props.data,
30
- props.hideRoot,
31
- props.getChildren,
32
- props.isOpen,
33
- props.disableDrag,
34
- props.disableDrop,
35
- props.openByDefault,
36
- ]
37
- );
38
-
15
+ const treeProps = useValidatedProps(props);
39
16
  return (
40
- <TreeViewProvider treeProps={props} imperativeHandle={ref} root={root}>
41
- <DndProvider
42
- backend={HTML5Backend}
43
- options={{ rootElement: props.dndRootElement || undefined }}
44
- >
45
- <OuterDrop>
46
- <List className={props.className} />
47
- </OuterDrop>
48
- <Preview />
49
- </DndProvider>
50
- </TreeViewProvider>
17
+ <TreeProvider treeProps={treeProps} imperativeHandle={ref}>
18
+ <OuterDrop>
19
+ <TreeContainer />
20
+ </OuterDrop>
21
+ <DragPreviewContainer />
22
+ </TreeProvider>
51
23
  );
52
24
  });
package/src/context.ts ADDED
@@ -0,0 +1,36 @@
1
+ import React, { createContext, useContext, useMemo } from "react";
2
+ import { TreeApi } from "./interfaces/tree-api";
3
+ import { RootState } from "./state/root-reducer";
4
+ import { IdObj } from "./types/utils";
5
+
6
+ export const TreeApiContext = createContext<TreeApi<any> | null>(null);
7
+
8
+ export function useTreeApi<T extends IdObj>() {
9
+ const value = useContext<TreeApi<T> | null>(
10
+ TreeApiContext as unknown as React.Context<TreeApi<T> | null>
11
+ );
12
+ if (value === null) throw new Error("No Tree Api Provided");
13
+ return value;
14
+ }
15
+
16
+ export const NodesContext = createContext<RootState["nodes"] | null>(null);
17
+
18
+ export function useNodesContext() {
19
+ const value = useContext(NodesContext);
20
+ if (value === null) throw new Error("Provide a NodesContext");
21
+ return value;
22
+ }
23
+
24
+ export const DndContext = createContext<RootState["dnd"] | null>(null);
25
+
26
+ export function useDndContext() {
27
+ const value = useContext(DndContext);
28
+ if (value === null) throw new Error("Provide a DnDContext");
29
+ return value;
30
+ }
31
+
32
+ export const DataUpdatesContext = createContext<number>(0);
33
+
34
+ export function useDataUpdates() {
35
+ useContext(DataUpdatesContext);
36
+ }
@@ -0,0 +1,9 @@
1
+ import { NodeApi } from "../interfaces/node-api";
2
+ import { IdObj } from "../types/utils";
3
+
4
+ export const createIndex = <T extends IdObj>(nodes: NodeApi<T>[]) => {
5
+ return nodes.reduce<{ [id: string]: number }>((map, node, index) => {
6
+ map[node.id] = index;
7
+ return map;
8
+ }, {});
9
+ };
@@ -0,0 +1,56 @@
1
+ import { NodeApi } from "../interfaces/node-api";
2
+ import { TreeApi } from "../interfaces/tree-api";
3
+ import { IdObj } from "../types/utils";
4
+
5
+ export function createList<T extends IdObj>(tree: TreeApi<T>) {
6
+ if (tree.isFiltered) {
7
+ return flattenAndFilterTree(tree.root, tree.isMatch.bind(tree));
8
+ } else {
9
+ return flattenTree(tree.root);
10
+ }
11
+ }
12
+
13
+ function flattenTree<T extends IdObj>(root: NodeApi<T>): NodeApi<T>[] {
14
+ const list: NodeApi<T>[] = [];
15
+ function collect(node: NodeApi<T>) {
16
+ if (node.level >= 0) {
17
+ list.push(node);
18
+ }
19
+ if (node.isOpen) {
20
+ node.children?.forEach(collect);
21
+ }
22
+ }
23
+ collect(root);
24
+ list.forEach(assignRowIndex);
25
+ return list;
26
+ }
27
+
28
+ function flattenAndFilterTree<T extends IdObj>(
29
+ root: NodeApi<T>,
30
+ isMatch: (n: NodeApi<T>) => boolean
31
+ ): NodeApi<T>[] {
32
+ function collect(node: NodeApi<T>) {
33
+ let result: NodeApi<T>[] = [];
34
+ const yes = !node.isRoot && isMatch(node);
35
+
36
+ if (node.children) {
37
+ for (let child of node.children) {
38
+ result = result.concat(collect(child));
39
+ }
40
+ }
41
+ if (result.length) {
42
+ if (!node.isRoot) result.unshift(node);
43
+ return result;
44
+ }
45
+ if (yes) return [node];
46
+ else return [];
47
+ }
48
+
49
+ const list = collect(root).filter((n) => n.parent?.isOpen);
50
+ list.forEach(assignRowIndex);
51
+ return list;
52
+ }
53
+
54
+ function assignRowIndex(node: NodeApi<any>, index: number) {
55
+ node.rowIndex = index;
56
+ }
@@ -0,0 +1,53 @@
1
+ import { IdObj } from "../types/utils";
2
+ import { NodeApi } from "../interfaces/node-api";
3
+ import { TreeApi } from "../interfaces/tree-api";
4
+
5
+ export const ROOT_ID = "__REACT_ARBORIST_INTERNAL_ROOT__";
6
+
7
+ export function createRoot<T extends IdObj>(tree: TreeApi<T>): NodeApi<T> {
8
+ function visitSelfAndChildren(
9
+ data: T,
10
+ level: number,
11
+ parent: NodeApi<T> | null
12
+ ) {
13
+ const node = new NodeApi<T>({
14
+ tree,
15
+ data,
16
+ level,
17
+ parent,
18
+ id: data.id,
19
+ children: null,
20
+ isDraggable: tree.isDraggable(data),
21
+ isDroppable: tree.isDroppable(data),
22
+ rowIndex: null,
23
+ });
24
+ const children = tree.getChildren(data);
25
+ if (children) {
26
+ node.children = children.map((child: T) =>
27
+ visitSelfAndChildren(child, level + 1, node)
28
+ );
29
+ }
30
+ return node;
31
+ }
32
+
33
+ const root = new NodeApi<T>({
34
+ tree,
35
+ id: ROOT_ID,
36
+ // @ts-ignore
37
+ data: { id: ROOT_ID },
38
+ level: -1,
39
+ parent: null,
40
+ children: null,
41
+ isDraggable: true,
42
+ isDroppable: true,
43
+ rowIndex: null,
44
+ });
45
+
46
+ const data: T[] = tree.props.data ?? [];
47
+
48
+ root.children = data.map((child) => {
49
+ return visitSelfAndChildren(child, 0, root);
50
+ });
51
+
52
+ return root;
53
+ }