react-arborist 1.1.0 → 1.2.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.
@@ -1,47 +1,12 @@
1
- import React, { CSSProperties } from "react";
2
- import { useCursorLocation, useStaticContext } from "../context";
1
+ import { useTreeApi } from "../context";
3
2
 
4
3
  export function DropCursor() {
5
- const treeView = useStaticContext();
6
- const cursor = useCursorLocation();
4
+ const tree = useTreeApi();
5
+ const cursor = tree.state.cursor;
7
6
  if (!cursor || cursor.type !== "line") return null;
8
- const top = treeView.rowHeight * cursor.index;
9
- const left = treeView.indent * cursor.level;
10
- const style: CSSProperties = {
11
- position: "absolute",
12
- pointerEvents: "none",
13
- top: top - 2 + "px",
14
- left: treeView.indent + left + "px",
15
- right: treeView.indent + "px",
16
- };
7
+ const indent = tree.indent;
8
+ const top = tree.rowHeight * cursor.index;
9
+ const left = indent * cursor.level;
17
10
 
18
- return <DefaultCursor style={style} />;
19
- }
20
-
21
- const placeholderStyle = {
22
- display: "flex",
23
- alignItems: "center",
24
- };
25
-
26
- const lineStyle = {
27
- flex: 1,
28
- height: "2px",
29
- background: "#4B91E2",
30
- borderRadius: "1px",
31
- };
32
-
33
- const circleStyle = {
34
- width: "4px",
35
- height: "4px",
36
- boxShadow: "0 0 0 3px #4B91E2",
37
- borderRadius: "50%",
38
- };
39
-
40
- function DefaultCursor({ style }: { style: CSSProperties }) {
41
- return (
42
- <div style={{ ...placeholderStyle, ...style }}>
43
- <div style={{ ...circleStyle }}></div>
44
- <div style={{ ...lineStyle }}></div>
45
- </div>
46
- );
11
+ return tree.renderDropCursor({ top, left, indent });
47
12
  }
@@ -0,0 +1,34 @@
1
+ import { forwardRef } from "react";
2
+ import { useTreeApi } from "../context";
3
+ import { DropCursor } from "./drop-cursor";
4
+
5
+ export const ListOuterElement = forwardRef(function Outer(
6
+ props: React.HTMLProps<HTMLDivElement>,
7
+ ref
8
+ ) {
9
+ const { children, ...rest } = props;
10
+ const tree = useTreeApi();
11
+ return (
12
+ <div
13
+ // @ts-ignore
14
+ ref={ref}
15
+ {...rest}
16
+ onClick={tree.onClick}
17
+ onContextMenu={tree.onContextMenu}
18
+ >
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>
31
+ {children}
32
+ </div>
33
+ );
34
+ });
@@ -0,0 +1,25 @@
1
+ import { FixedSizeList } from "react-window";
2
+ import { useTreeApi } from "../context";
3
+ import { ListOuterElement } from "./list-outer-element";
4
+ import { Row } from "./row";
5
+
6
+ export function List(props: { className?: string }) {
7
+ const tree = useTreeApi();
8
+ return (
9
+ <div style={{ height: tree.height, width: tree.width, overflow: "hidden" }}>
10
+ <FixedSizeList
11
+ className={props.className}
12
+ outerRef={tree.listEl}
13
+ itemCount={tree.visibleNodes.length}
14
+ height={tree.height}
15
+ width={tree.width}
16
+ itemSize={tree.rowHeight}
17
+ itemKey={(index) => tree.visibleNodes[index]?.id || index}
18
+ outerElementType={ListOuterElement}
19
+ ref={tree.list}
20
+ >
21
+ {Row}
22
+ </FixedSizeList>
23
+ </div>
24
+ );
25
+ }
@@ -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
+ }
@@ -1,7 +1,7 @@
1
1
  import React, { CSSProperties, memo } from "react";
2
2
  import { useDragLayer, XYCoord } from "react-dnd";
3
- import { useStaticContext } from "../context";
4
- import { DragItem } from "../types";
3
+ import { useTreeApi } from "../context";
4
+ import { DragItem, IdObj } from "../types";
5
5
 
6
6
  const layerStyles: CSSProperties = {
7
7
  position: "fixed",
@@ -70,12 +70,12 @@ function Count(props: { item: DragItem; mouse: XYCoord | null }) {
70
70
  else return null;
71
71
  }
72
72
 
73
- const PreviewNode = memo(function PreviewNode(props: {
73
+ const PreviewNode = memo(function PreviewNode<T extends IdObj>(props: {
74
74
  item: DragItem | null;
75
75
  }) {
76
- const tree = useStaticContext();
76
+ const tree = useTreeApi<T>();
77
77
  if (!props.item) return null;
78
- const node = tree.api.getNode(props.item.id);
78
+ const node = tree.getNode(props.item.id);
79
79
  if (!node) return null;
80
80
  return (
81
81
  <tree.renderer
@@ -86,7 +86,7 @@ const PreviewNode = memo(function PreviewNode(props: {
86
86
  row: {},
87
87
  indent: { paddingLeft: node.level * tree.indent },
88
88
  }}
89
- tree={tree.api}
89
+ tree={tree}
90
90
  state={{
91
91
  isDragging: false,
92
92
  isEditing: false,
@@ -1,36 +1,34 @@
1
1
  import React, { useCallback, useMemo, useRef } from "react";
2
- import {
3
- useCursorParentId,
4
- useEditingId,
5
- useIsCursorOverFolder,
6
- useIsSelected,
7
- useStaticContext,
8
- } from "../context";
2
+ import { useTreeApi } from "../context";
9
3
  import { useDragHook } from "../dnd/drag-hook";
10
4
  import { useDropHook } from "../dnd/drop-hook";
5
+ import { IdObj } from "../types";
11
6
 
12
7
  type Props = {
13
8
  style: React.CSSProperties;
14
9
  index: number;
15
10
  };
16
11
 
17
- export const Row = React.memo(function Row({ index, style }: Props) {
18
- const tree = useStaticContext();
19
- const selected = useIsSelected();
20
- const node = tree.api.visibleNodes[index];
21
- const next = tree.api.visibleNodes[index + 1] || null;
22
- const prev = tree.api.visibleNodes[index - 1] || null;
23
- const cursorParentId = useCursorParentId();
24
- const cursorOverFolder = useIsCursorOverFolder();
12
+ export const Row = React.memo(function Row<T extends IdObj>({
13
+ index,
14
+ style,
15
+ }: Props) {
16
+ const realTree = useTreeApi<T>();
17
+ const tree = useMemo(() => realTree, []);
18
+ tree.sync(realTree);
19
+
20
+ const node = tree.visibleNodes[index];
21
+ const next = tree.visibleNodes[index + 1] || null;
22
+ const prev = tree.visibleNodes[index - 1] || null;
25
23
  const el = useRef<HTMLDivElement | null>(null);
26
24
  const [{ isDragging }, dragRef] = useDragHook(node);
27
25
  const [, dropRef] = useDropHook(el, node, prev, next);
28
- const isEditing = node.id === useEditingId();
29
- const isSelected = selected(index);
30
- const nextSelected = next && selected(index + 1);
31
- const prevSelected = prev && selected(index - 1);
32
- const isHoveringOverChild = node.id === cursorParentId;
33
- const isOverFolder = node.id === cursorParentId && cursorOverFolder;
26
+ const isEditing = node.id === tree.editingId;
27
+ const isSelected = tree.isSelected(index);
28
+ const nextSelected = next && tree.isSelected(index + 1);
29
+ const prevSelected = prev && tree.isSelected(index - 1);
30
+ const isHoveringOverChild = node.id === tree.cursorParentId;
31
+ const isOverFolder = node.id === tree.cursorParentId && tree.cursorOverFolder;
34
32
  const isOpen = node.isOpen;
35
33
  const indent = tree.indent * node.level;
36
34
  const state = useMemo(() => {
@@ -79,22 +77,22 @@ export const Row = React.memo(function Row({ index, style }: Props) {
79
77
  ) => {
80
78
  if (node.rowIndex === null) return;
81
79
  if (args.selectOnClick || e.metaKey || e.shiftKey) {
82
- tree.api.select(node.rowIndex, e.metaKey, e.shiftKey);
80
+ tree.select(node.rowIndex, e.metaKey, e.shiftKey);
83
81
  } else {
84
- tree.api.select(null, false, false);
82
+ tree.select(null, false, false);
85
83
  }
86
84
  },
87
85
  toggle: (e: React.MouseEvent) => {
88
86
  e.stopPropagation();
89
87
  tree.onToggle(node.id, !node.isOpen);
90
88
  },
91
- edit: () => tree.api.edit(node.id),
89
+ edit: () => tree.edit(node.id),
92
90
  submit: (name: string) => {
93
- name.trim() ? tree.api.submit(node.id, name) : tree.api.reset(node.id);
91
+ name.trim() ? tree.submit(node.id, name) : tree.reset(node.id);
94
92
  },
95
- reset: () => tree.api.reset(node.id),
93
+ reset: () => tree.reset(node.id),
96
94
  };
97
- }, [tree, node]);
95
+ }, [node, tree]);
98
96
 
99
97
  const Renderer = useMemo(() => {
100
98
  return React.memo(tree.renderer);
@@ -108,7 +106,7 @@ export const Row = React.memo(function Row({ index, style }: Props) {
108
106
  state={state}
109
107
  handlers={handlers}
110
108
  preview={false}
111
- tree={tree.api}
109
+ tree={tree}
112
110
  />
113
111
  );
114
112
  });
@@ -1,81 +1,14 @@
1
- import {
2
- forwardRef,
3
- MouseEventHandler,
4
- ReactElement,
5
- useMemo,
6
- useRef,
7
- } from "react";
1
+ import { forwardRef, ReactElement, useMemo, useRef } from "react";
8
2
  import { DndProvider } from "react-dnd";
9
3
  import { HTML5Backend } from "react-dnd-html5-backend";
10
- import { FixedSizeList } from "react-window";
11
- import { useStaticContext } from "../context";
12
4
  import { enrichTree } from "../data/enrich-tree";
13
- import { useOuterDrop } from "../dnd/outer-drop-hook";
14
5
  import { TreeViewProvider } from "../provider";
15
6
  import { TreeApi } from "../tree-api";
16
7
  import { IdObj, Node, TreeProps } from "../types";
17
8
  import { noop } from "../utils";
18
- import { DropCursor } from "./drop-cursor";
19
9
  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
- }
10
+ import { OuterDrop } from "./outer-drop";
11
+ import { List } from "./list";
79
12
 
80
13
  export const Tree = forwardRef(function Tree<T extends IdObj>(
81
14
  props: TreeProps<T>,
@@ -104,21 +37,7 @@ export const Tree = forwardRef(function Tree<T extends IdObj>(
104
37
  );
105
38
 
106
39
  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
- >
40
+ <TreeViewProvider treeProps={props} imperativeHandle={ref} root={root}>
122
41
  <DndProvider
123
42
  backend={HTML5Backend}
124
43
  options={{ rootElement: props.dndRootElement || undefined }}
package/src/context.tsx CHANGED
@@ -1,52 +1,13 @@
1
- import { createContext, useContext, useMemo } from "react";
2
- import { Cursor } from "./dnd/compute-drop";
3
- import { Selection } from "./selection/selection";
4
- import { IdObj, SelectionState, StaticContext } from "./types";
1
+ import React, { createContext, useContext, useMemo } from "react";
2
+ import { TreeApi } from "./tree-api";
3
+ import { IdObj } from "./types";
5
4
 
6
- export const CursorParentId = createContext<string | null>(null);
7
- export function useCursorParentId() {
8
- return useContext(CursorParentId);
9
- }
10
-
11
- export const IsCursorOverFolder = createContext<boolean>(false);
12
- export function useIsCursorOverFolder() {
13
- return useContext(IsCursorOverFolder);
14
- }
5
+ export const TreeApiContext = createContext<TreeApi<any> | null>(null);
15
6
 
16
- export const CursorLocationContext = createContext<Cursor | null>(null);
17
- export function useCursorLocation() {
18
- return useContext(CursorLocationContext);
19
- }
20
-
21
- export const Static = createContext<StaticContext<IdObj> | null>(null);
22
- export function useStaticContext() {
23
- const value = useContext(Static);
24
- if (!value) throw new Error("Context must be in a provider");
7
+ export function useTreeApi<T extends IdObj>() {
8
+ const value = useContext<TreeApi<T> | null>(
9
+ TreeApiContext as unknown as React.Context<TreeApi<T> | null>
10
+ );
11
+ if (value === null) throw new Error("No Tree Api Provided");
25
12
  return value;
26
13
  }
27
-
28
- export const DispatchContext = createContext(null);
29
- export function useDispatch() {
30
- const dispatch = useContext(DispatchContext);
31
- if (!dispatch) throw new Error("No dispatch provided");
32
- return dispatch;
33
- }
34
-
35
- export const SelectionContext = createContext<SelectionState | null>(null);
36
- export function useSelectedIds(): string[] {
37
- const value = useContext(SelectionContext);
38
- if (!value) throw new Error("Must provide selection context");
39
- return value.ids;
40
- }
41
-
42
- export function useIsSelected(): (index: number | null) => boolean {
43
- const value = useContext(SelectionContext);
44
- if (!value) throw new Error("Must provide selection context");
45
- const s = useMemo(() => Selection.parse(value.data, []), [value.data]);
46
- return (i) => s.contains(i);
47
- }
48
-
49
- export const EditingIdContext = createContext<string | null>(null);
50
- export function useEditingId(): string | null {
51
- return useContext(EditingIdContext);
52
- }
@@ -1,7 +1,7 @@
1
1
  import { useEffect } from "react";
2
2
  import { ConnectDragSource, useDrag } from "react-dnd";
3
3
  import { getEmptyImage } from "react-dnd-html5-backend";
4
- import { useIsSelected, useSelectedIds, useStaticContext } from "../context";
4
+ import { useTreeApi } from "../context";
5
5
  import { DragItem, Node } from "../types";
6
6
  import { DropResult } from "./drop-hook";
7
7
 
@@ -10,9 +10,8 @@ type CollectedProps = { isDragging: boolean };
10
10
  export function useDragHook(
11
11
  node: Node
12
12
  ): [{ isDragging: boolean }, ConnectDragSource] {
13
- const tree = useStaticContext();
14
- const isSelected = useIsSelected();
15
- const ids = useSelectedIds();
13
+ const tree = useTreeApi();
14
+ const ids = tree.getSelectedIds();
16
15
  const [{ isDragging }, ref, preview] = useDrag<
17
16
  DragItem,
18
17
  DropResult,
@@ -23,13 +22,13 @@ export function useDragHook(
23
22
  type: "NODE",
24
23
  item: () => ({
25
24
  id: node.id,
26
- dragIds: isSelected(node.rowIndex) ? ids : [node.id],
25
+ dragIds: tree.isSelected(node.rowIndex) ? ids : [node.id],
27
26
  }),
28
27
  collect: (m) => ({
29
28
  isDragging: m.isDragging(),
30
29
  }),
31
30
  end: (item, monitor) => {
32
- tree.api.hideCursor();
31
+ tree.hideCursor();
33
32
  const drop = monitor.getDropResult();
34
33
  if (drop && drop.parentId) {
35
34
  tree.onMove(item.dragIds, drop.parentId, drop.index);
@@ -1,6 +1,6 @@
1
1
  import { RefObject } from "react";
2
2
  import { ConnectDropTarget, useDrop } from "react-dnd";
3
- import { useStaticContext } from "../context";
3
+ import { useTreeApi } from "../context";
4
4
  import { DragItem, Node } from "../types";
5
5
  import { isDecendent, isFolder } from "../utils";
6
6
  import { computeDrop } from "./compute-drop";
@@ -18,13 +18,13 @@ export function useDropHook(
18
18
  prev: Node | null,
19
19
  next: Node | null
20
20
  ): [CollectedProps, ConnectDropTarget] {
21
- const tree = useStaticContext();
21
+ const tree = useTreeApi();
22
22
  return useDrop<DragItem, DropResult | null, CollectedProps>(
23
23
  () => ({
24
24
  accept: "NODE",
25
25
  canDrop: (item) => {
26
26
  for (let id of item.dragIds) {
27
- const drag = tree.api.getNode(id);
27
+ const drag = tree.getNode(id);
28
28
  if (!drag) return false;
29
29
  if (isFolder(drag) && isDecendent(node, drag)) return false;
30
30
  }
@@ -42,9 +42,9 @@ export function useDropHook(
42
42
  prevNode: prev,
43
43
  nextNode: next,
44
44
  });
45
- if (cursor) tree.api.showCursor(cursor);
45
+ if (cursor) tree.showCursor(cursor);
46
46
  } else {
47
- tree.api.hideCursor();
47
+ tree.hideCursor();
48
48
  }
49
49
  },
50
50
  drop: (item, m): DropResult | undefined | null => {
@@ -1,11 +1,11 @@
1
1
  import { useDrop } from "react-dnd";
2
- import { useStaticContext } from "../context";
2
+ import { useTreeApi } from "../context";
3
3
  import { DragItem } from "../types";
4
4
  import { computeDrop } from "./compute-drop";
5
5
  import { DropResult } from "./drop-hook";
6
6
 
7
7
  export function useOuterDrop() {
8
- const tree = useStaticContext();
8
+ const tree = useTreeApi();
9
9
 
10
10
  // In case we drop an item at the bottom of the list
11
11
  const [, drop] = useDrop<DragItem, DropResult | null, { isOver: boolean }>(
@@ -20,10 +20,10 @@ export function useOuterDrop() {
20
20
  offset: offset,
21
21
  indent: tree.indent,
22
22
  node: null,
23
- prevNode: tree.api.visibleNodes[tree.api.visibleNodes.length - 1],
23
+ prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
24
24
  nextNode: null,
25
25
  });
26
- if (cursor) tree.api.showCursor(cursor);
26
+ if (cursor) tree.showCursor(cursor);
27
27
  },
28
28
  canDrop: (item, m) => {
29
29
  return m.isOver({ shallow: true });
@@ -37,7 +37,7 @@ export function useOuterDrop() {
37
37
  offset: offset,
38
38
  indent: tree.indent,
39
39
  node: null,
40
- prevNode: tree.api.visibleNodes[tree.api.visibleNodes.length - 1],
40
+ prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
41
41
  nextNode: null,
42
42
  });
43
43
  return drop;
package/src/index.ts CHANGED
@@ -1,5 +1,19 @@
1
1
  import { Tree } from "./components/tree";
2
2
  import { TreeApi } from "./tree-api";
3
- import type { NodeRenderer, NodeState, NodeHandlers } from "./types";
3
+ import type {
4
+ NodeRenderer,
5
+ NodeState,
6
+ NodeHandlers,
7
+ NodeRendererProps,
8
+ DropCursorProps,
9
+ } from "./types";
4
10
 
5
- export { Tree, TreeApi, NodeRenderer, NodeState, NodeHandlers };
11
+ export {
12
+ Tree,
13
+ TreeApi,
14
+ NodeRenderer,
15
+ NodeState,
16
+ NodeHandlers,
17
+ NodeRendererProps,
18
+ DropCursorProps,
19
+ };
package/src/provider.tsx CHANGED
@@ -1,61 +1,41 @@
1
- import { useImperativeHandle, useMemo, useReducer, useRef } from "react";
2
- import { FixedSizeList } from "react-window";
3
1
  import {
4
- CursorLocationContext,
5
- CursorParentId,
6
- EditingIdContext,
7
- IsCursorOverFolder,
8
- SelectionContext,
9
- Static,
10
- } from "./context";
11
- import { Cursor } from "./dnd/compute-drop";
12
- import { initState, reducer } from "./reducer";
2
+ useImperativeHandle,
3
+ useLayoutEffect,
4
+ useMemo,
5
+ useReducer,
6
+ useRef,
7
+ } from "react";
8
+ import { FixedSizeList } from "react-window";
9
+ import { TreeApiContext } from "./context";
10
+ import { actions, initState, reducer } from "./reducer";
13
11
  import { useSelectionKeys } from "./selection/selection-hook";
14
- import { useTreeApi } from "./tree-api-hook";
15
- import { StateContext, StaticContext, TreeProviderProps } from "./types";
12
+ import { TreeApi } from "./tree-api";
13
+ import { IdObj, TreeProviderProps } from "./types";
16
14
 
17
- export function TreeViewProvider<T>(props: TreeProviderProps<T>) {
15
+ export function TreeViewProvider<T extends IdObj>(props: TreeProviderProps<T>) {
18
16
  const [state, dispatch] = useReducer(reducer, initState());
19
- const list = useRef<FixedSizeList>();
20
- const api = useTreeApi<T>(state, dispatch, props, list.current);
17
+ const list = useRef<FixedSizeList | null>(null);
18
+ const listEl = useRef<HTMLDivElement | null>(null);
21
19
 
22
- useImperativeHandle(props.imperativeHandle, () => api);
23
- useSelectionKeys(props.listEl, api);
24
- const staticValue = useMemo<StaticContext<T>>(
25
- () => ({ ...props, api, list }),
26
- [props, api, list]
20
+ const api = useMemo(
21
+ () => new TreeApi<T>(dispatch, state, props, list, listEl),
22
+ [dispatch, state, props, list, listEl]
27
23
  );
28
24
 
29
25
  /**
30
- * This context pattern is ridiculous, next time use redux.
26
+ * This ensures that the selection remains correct even
27
+ * after opening and closing a folders
31
28
  */
32
- return (
33
- // @ts-ignore
34
- <Static.Provider value={staticValue}>
35
- <EditingIdContext.Provider value={state.editingId}>
36
- <SelectionContext.Provider value={state.selection}>
37
- <CursorParentId.Provider value={getParentId(state.cursor)}>
38
- <IsCursorOverFolder.Provider value={isOverFolder(state)}>
39
- <CursorLocationContext.Provider value={state.cursor}>
40
- {props.children}
41
- </CursorLocationContext.Provider>
42
- </IsCursorOverFolder.Provider>
43
- </CursorParentId.Provider>
44
- </SelectionContext.Provider>
45
- </EditingIdContext.Provider>
46
- </Static.Provider>
47
- );
48
- }
29
+ useLayoutEffect(() => {
30
+ dispatch(actions.setVisibleIds(api.visibleIds, api.idToIndex));
31
+ }, [dispatch, api.visibleIds, api.idToIndex, props.root]);
49
32
 
50
- function getParentId(cursor: Cursor) {
51
- switch (cursor.type) {
52
- case "highlight":
53
- return cursor.id;
54
- default:
55
- return null;
56
- }
57
- }
33
+ useImperativeHandle(props.imperativeHandle, () => api);
34
+ useSelectionKeys(listEl, api);
58
35
 
59
- function isOverFolder(state: StateContext) {
60
- return state.cursor.type === "highlight";
36
+ return (
37
+ <TreeApiContext.Provider value={api}>
38
+ {props.children}
39
+ </TreeApiContext.Provider>
40
+ );
61
41
  }
@@ -1,7 +1,8 @@
1
1
  import { MutableRefObject, useEffect } from "react";
2
2
  import { TreeApi } from "../tree-api";
3
+ import { IdObj } from "../types";
3
4
 
4
- export function useSelectionKeys<T>(
5
+ export function useSelectionKeys<T extends IdObj>(
5
6
  ref: MutableRefObject<HTMLDivElement | null>,
6
7
  api: TreeApi<T>
7
8
  ) {