react-arborist 1.1.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 (122) 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/list-outer-element.d.ts +2 -0
  10. package/dist/components/outer-drop.d.ts +4 -0
  11. package/dist/components/provider.d.ts +11 -0
  12. package/dist/components/row-container.d.ts +8 -0
  13. package/dist/components/tree-container.d.ts +2 -0
  14. package/dist/components/tree.d.ts +5 -4
  15. package/dist/context.d.ts +23 -18
  16. package/dist/data/create-index.d.ts +5 -0
  17. package/dist/data/create-list.d.ts +4 -0
  18. package/dist/data/create-root.d.ts +5 -0
  19. package/dist/data/flatten-tree.d.ts +4 -2
  20. package/dist/data/simple-tree.d.ts +43 -0
  21. package/dist/dnd/compute-drop.d.ts +4 -4
  22. package/dist/dnd/drag-hook.d.ts +3 -4
  23. package/dist/dnd/drop-hook.d.ts +2 -3
  24. package/dist/hooks/use-fresh-node.d.ts +2 -0
  25. package/dist/hooks/use-simple-tree.d.ts +13 -0
  26. package/dist/hooks/use-uncontrolled-tree.d.ts +24 -0
  27. package/dist/hooks/use-validated-props.d.ts +3 -0
  28. package/dist/index.d.ts +8 -4
  29. package/dist/index.js +2093 -1184
  30. package/dist/index.js.map +1 -1
  31. package/dist/interfaces/node-api.d.ts +67 -0
  32. package/dist/interfaces/tree-api.d.ts +112 -0
  33. package/dist/module.js +2082 -1192
  34. package/dist/module.js.map +1 -1
  35. package/dist/state/dnd-slice.d.ts +20 -0
  36. package/dist/state/drag-slice.d.ts +7 -0
  37. package/dist/state/edit-slice.d.ts +8 -0
  38. package/dist/state/focus-slice.d.ts +12 -0
  39. package/dist/state/initial.d.ts +3 -0
  40. package/dist/state/open-slice.d.ts +30 -0
  41. package/dist/state/root-reducer.d.ts +13 -0
  42. package/dist/state/selection-slice.d.ts +36 -0
  43. package/dist/types/dnd.d.ts +9 -0
  44. package/dist/types/handlers.d.ts +24 -0
  45. package/dist/types/renderers.d.ts +30 -0
  46. package/dist/types/state.d.ts +2 -0
  47. package/dist/types/tree-props.d.ts +43 -0
  48. package/dist/types/utils.d.ts +21 -0
  49. package/dist/utils/props.d.ts +3 -0
  50. package/dist/utils.d.ts +15 -6
  51. package/package.json +10 -7
  52. package/src/components/cursor.tsx +15 -0
  53. package/src/components/default-container.tsx +229 -0
  54. package/src/components/{drop-cursor.tsx → default-cursor.tsx} +15 -20
  55. package/src/components/default-drag-preview.tsx +92 -0
  56. package/src/components/default-node.tsx +15 -0
  57. package/src/components/default-row.tsx +21 -0
  58. package/src/components/drag-preview-container.tsx +26 -0
  59. package/src/components/list-inner-element.tsx +22 -0
  60. package/src/components/list-outer-element.tsx +45 -0
  61. package/src/components/outer-drop.ts +7 -0
  62. package/src/components/provider.tsx +97 -0
  63. package/src/components/row-container.tsx +82 -0
  64. package/src/components/tree-container.tsx +13 -0
  65. package/src/components/tree.tsx +17 -126
  66. package/src/context.ts +36 -0
  67. package/src/data/create-index.ts +9 -0
  68. package/src/data/create-list.ts +56 -0
  69. package/src/data/create-root.ts +53 -0
  70. package/src/data/simple-tree.ts +103 -0
  71. package/src/dnd/compute-drop.ts +16 -16
  72. package/src/dnd/drag-hook.ts +28 -23
  73. package/src/dnd/drop-hook.ts +35 -21
  74. package/src/dnd/outer-drop-hook.ts +6 -6
  75. package/src/hooks/use-fresh-node.ts +16 -0
  76. package/src/hooks/use-simple-tree.ts +55 -0
  77. package/src/hooks/use-validated-props.ts +35 -0
  78. package/src/index.ts +9 -5
  79. package/src/interfaces/node-api.ts +187 -0
  80. package/src/interfaces/tree-api.ts +552 -0
  81. package/src/state/dnd-slice.ts +36 -0
  82. package/src/state/drag-slice.ts +31 -0
  83. package/src/state/edit-slice.ts +19 -0
  84. package/src/state/focus-slice.ts +28 -0
  85. package/src/state/initial.ts +14 -0
  86. package/src/state/open-slice.ts +53 -0
  87. package/src/state/root-reducer.ts +21 -0
  88. package/src/state/selection-slice.ts +75 -0
  89. package/src/types/dnd.ts +10 -0
  90. package/src/types/handlers.ts +24 -0
  91. package/src/types/renderers.ts +34 -0
  92. package/src/types/state.ts +3 -0
  93. package/src/types/tree-props.ts +63 -0
  94. package/src/types/utils.ts +26 -0
  95. package/src/utils/props.ts +8 -0
  96. package/src/utils.ts +125 -11
  97. package/README.md +0 -220
  98. package/dist/components/preview.d.ts +0 -2
  99. package/dist/components/row.d.ts +0 -7
  100. package/dist/data/enrich-tree.d.ts +0 -2
  101. package/dist/provider.d.ts +0 -3
  102. package/dist/reducer.d.ts +0 -46
  103. package/dist/selection/range.d.ts +0 -13
  104. package/dist/selection/selection-hook.d.ts +0 -3
  105. package/dist/selection/selection.d.ts +0 -33
  106. package/dist/tree-api-hook.d.ts +0 -6
  107. package/dist/tree-api.d.ts +0 -34
  108. package/dist/types.d.ts +0 -131
  109. package/src/components/preview.tsx +0 -108
  110. package/src/components/row.tsx +0 -114
  111. package/src/context.tsx +0 -52
  112. package/src/data/enrich-tree.ts +0 -74
  113. package/src/data/flatten-tree.ts +0 -17
  114. package/src/provider.tsx +0 -61
  115. package/src/reducer.ts +0 -161
  116. package/src/selection/range.ts +0 -41
  117. package/src/selection/selection-hook.ts +0 -24
  118. package/src/selection/selection.test.ts +0 -111
  119. package/src/selection/selection.ts +0 -186
  120. package/src/tree-api-hook.ts +0 -34
  121. package/src/tree-api.ts +0 -156
  122. package/src/types.ts +0 -155
@@ -0,0 +1,92 @@
1
+ import React, { CSSProperties, memo } from "react";
2
+ import { XYCoord } from "react-dnd";
3
+ import { useTreeApi } from "../context";
4
+ import { DragPreviewProps } from "../types/renderers";
5
+ import { IdObj } from "../types/utils";
6
+
7
+ const layerStyles: CSSProperties = {
8
+ position: "fixed",
9
+ pointerEvents: "none",
10
+ zIndex: 100,
11
+ left: 0,
12
+ top: 0,
13
+ width: "100%",
14
+ height: "100%",
15
+ };
16
+
17
+ const getStyle = (offset: XYCoord | null) => {
18
+ if (!offset) return { display: "none" };
19
+ const { x, y } = offset;
20
+ return { transform: `translate(${x}px, ${y}px)` };
21
+ };
22
+
23
+ const getCountStyle = (offset: XYCoord | null) => {
24
+ if (!offset) return { display: "none" };
25
+ const { x, y } = offset;
26
+ return { transform: `translate(${x + 10}px, ${y + 10}px)` };
27
+ };
28
+
29
+ export function DefaultDragPreview({
30
+ offset,
31
+ mouse,
32
+ id,
33
+ dragIds,
34
+ isDragging,
35
+ }: DragPreviewProps) {
36
+ return (
37
+ <Overlay isDragging={isDragging}>
38
+ <Position offset={offset}>
39
+ <PreviewNode id={id} dragIds={dragIds} />
40
+ </Position>
41
+ <Count mouse={mouse} count={dragIds.length} />
42
+ </Overlay>
43
+ );
44
+ }
45
+
46
+ const Overlay = memo(function Overlay(props: {
47
+ children: JSX.Element[];
48
+ isDragging: boolean;
49
+ }) {
50
+ if (!props.isDragging) return null;
51
+ return <div style={layerStyles}>{props.children}</div>;
52
+ });
53
+
54
+ function Position(props: { children: JSX.Element; offset: XYCoord | null }) {
55
+ return (
56
+ <div className="row preview" style={getStyle(props.offset)}>
57
+ {props.children}
58
+ </div>
59
+ );
60
+ }
61
+
62
+ function Count(props: { count: number; mouse: XYCoord | null }) {
63
+ const { count, mouse } = props;
64
+ if (count > 1)
65
+ return (
66
+ <div className="selected-count" style={getCountStyle(mouse)}>
67
+ {count}
68
+ </div>
69
+ );
70
+ else return null;
71
+ }
72
+
73
+ const PreviewNode = memo(function PreviewNode<T extends IdObj>(props: {
74
+ id: string | null;
75
+ dragIds: string[];
76
+ }) {
77
+ const tree = useTreeApi<T>();
78
+ const node = tree.get(props.id);
79
+ if (!node) return null;
80
+ return (
81
+ <tree.renderNode
82
+ preview
83
+ node={node}
84
+ style={{
85
+ paddingLeft: node.level * tree.indent,
86
+ opacity: 0.2,
87
+ background: "transparent",
88
+ }}
89
+ tree={tree}
90
+ />
91
+ );
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
+ });
@@ -0,0 +1,45 @@
1
+ import { forwardRef } from "react";
2
+ import { useTreeApi } from "../context";
3
+ import { treeBlur } from "../state/focus-slice";
4
+ import { DropCursor } from "./cursor";
5
+
6
+ export const ListOuterElement = forwardRef(function Outer(
7
+ props: React.HTMLProps<HTMLDivElement>,
8
+ ref
9
+ ) {
10
+ const { children, ...rest } = props;
11
+ const tree = useTreeApi();
12
+ return (
13
+ <div
14
+ // @ts-ignore
15
+ ref={ref}
16
+ {...rest}
17
+ onClick={(e) => {
18
+ if (e.currentTarget === e.target) tree.selectNone();
19
+ }}
20
+ >
21
+ <DropContainer />
22
+ {children}
23
+ </div>
24
+ );
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,7 @@
1
+ import { ReactElement } from "react";
2
+ import { useOuterDrop } from "../dnd/outer-drop-hook";
3
+
4
+ export function OuterDrop(props: { children: ReactElement }) {
5
+ useOuterDrop();
6
+ return props.children;
7
+ }
@@ -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,133 +1,24 @@
1
- import {
2
- forwardRef,
3
- MouseEventHandler,
4
- ReactElement,
5
- useMemo,
6
- useRef,
7
- } from "react";
8
- import { DndProvider } from "react-dnd";
9
- import { HTML5Backend } from "react-dnd-html5-backend";
10
- import { FixedSizeList } from "react-window";
11
- import { useStaticContext } from "../context";
12
- import { enrichTree } from "../data/enrich-tree";
13
- import { useOuterDrop } from "../dnd/outer-drop-hook";
14
- import { TreeViewProvider } from "../provider";
15
- import { TreeApi } from "../tree-api";
16
- import { IdObj, Node, TreeProps } from "../types";
17
- import { noop } from "../utils";
18
- import { DropCursor } from "./drop-cursor";
19
- import { Preview } from "./preview";
20
- import { Row } from "./row";
21
-
22
- const OuterElement = forwardRef(function Outer(
23
- props: React.HTMLProps<HTMLDivElement>,
24
- ref
25
- ) {
26
- const { children, ...rest } = props;
27
- const tree = useStaticContext();
28
- return (
29
- <div
30
- // @ts-ignore
31
- ref={ref}
32
- {...rest}
33
- onClick={tree.onClick}
34
- onContextMenu={tree.onContextMenu}
35
- >
36
- <div
37
- style={{
38
- height: tree.api.visibleNodes.length * tree.rowHeight,
39
- width: "100%",
40
- overflow: "hidden",
41
- position: "absolute",
42
- left: "0",
43
- right: "0",
44
- }}
45
- >
46
- <DropCursor />
47
- </div>
48
- {children}
49
- </div>
50
- );
51
- });
52
-
53
- function List(props: { className?: string }) {
54
- const tree = useStaticContext();
55
- return (
56
- <div style={{ height: tree.height, width: tree.width, overflow: "hidden" }}>
57
- <FixedSizeList
58
- className={props.className}
59
- outerRef={tree.listEl}
60
- itemCount={tree.api.visibleNodes.length}
61
- height={tree.height}
62
- width={tree.width}
63
- itemSize={tree.rowHeight}
64
- itemKey={(index) => tree.api.visibleNodes[index]?.id || index}
65
- outerElementType={OuterElement}
66
- // @ts-ignore
67
- ref={tree.list}
68
- >
69
- {Row}
70
- </FixedSizeList>
71
- </div>
72
- );
73
- }
74
-
75
- function OuterDrop(props: { children: ReactElement }) {
76
- useOuterDrop();
77
- return props.children;
78
- }
1
+ import { forwardRef } from "react";
2
+ import { TreeProvider } from "./provider";
3
+ import { TreeApi } from "../interfaces/tree-api";
4
+ import { OuterDrop } from "./outer-drop";
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";
79
10
 
80
11
  export const Tree = forwardRef(function Tree<T extends IdObj>(
81
12
  props: TreeProps<T>,
82
- ref: React.Ref<TreeApi<T>>
13
+ ref: React.Ref<TreeApi<T> | undefined>
83
14
  ) {
84
- const root = useMemo<Node<T>>(
85
- () =>
86
- enrichTree<T>(
87
- props.data,
88
- props.hideRoot,
89
- props.getChildren,
90
- props.isOpen,
91
- props.disableDrag,
92
- props.disableDrop,
93
- props.openByDefault
94
- ),
95
- [
96
- props.data,
97
- props.hideRoot,
98
- props.getChildren,
99
- props.isOpen,
100
- props.disableDrag,
101
- props.disableDrop,
102
- props.openByDefault,
103
- ]
104
- );
105
-
15
+ const treeProps = useValidatedProps(props);
106
16
  return (
107
- <TreeViewProvider
108
- imperativeHandle={ref}
109
- root={root}
110
- listEl={useRef<HTMLDivElement | null>(null)}
111
- renderer={props.children}
112
- width={props.width === undefined ? 300 : props.width}
113
- height={props.height === undefined ? 500 : props.height}
114
- indent={props.indent === undefined ? 24 : props.indent}
115
- rowHeight={props.rowHeight === undefined ? 24 : props.rowHeight}
116
- onMove={props.onMove || noop}
117
- onToggle={props.onToggle || noop}
118
- onEdit={props.onEdit || noop}
119
- onClick={props.onClick}
120
- onContextMenu={props.onContextMenu}
121
- >
122
- <DndProvider
123
- backend={HTML5Backend}
124
- options={{ rootElement: props.dndRootElement || undefined }}
125
- >
126
- <OuterDrop>
127
- <List className={props.className} />
128
- </OuterDrop>
129
- <Preview />
130
- </DndProvider>
131
- </TreeViewProvider>
17
+ <TreeProvider treeProps={treeProps} imperativeHandle={ref}>
18
+ <OuterDrop>
19
+ <TreeContainer />
20
+ </OuterDrop>
21
+ <DragPreviewContainer />
22
+ </TreeProvider>
132
23
  );
133
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
+ }