react-arborist 3.7.0 → 3.8.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.
Files changed (86) hide show
  1. package/dist/main/components/cursor.js +1 -2
  2. package/dist/main/components/default-container.js +31 -2
  3. package/dist/main/components/default-cursor.js +1 -1
  4. package/dist/main/components/default-drag-preview.d.ts +1 -1
  5. package/dist/main/components/default-drag-preview.js +1 -1
  6. package/dist/main/components/default-row.d.ts +1 -1
  7. package/dist/main/components/default-row.js +1 -1
  8. package/dist/main/components/list-outer-element.js +1 -1
  9. package/dist/main/components/provider.d.ts +1 -1
  10. package/dist/main/components/provider.js +2 -2
  11. package/dist/main/components/provider.test.js +70 -0
  12. package/dist/main/components/row-container.d.ts +1 -1
  13. package/dist/main/components/row-container.js +2 -3
  14. package/dist/main/dnd/drag-hook.js +1 -0
  15. package/dist/main/hooks/use-validated-props.js +1 -2
  16. package/dist/main/interfaces/node-api.js +4 -1
  17. package/dist/main/interfaces/tree-api.d.ts +27 -5
  18. package/dist/main/interfaces/tree-api.js +98 -14
  19. package/dist/main/interfaces/tree-api.test.js +31 -0
  20. package/dist/main/state/drag-slice.js +1 -2
  21. package/dist/main/types/state.d.ts +1 -1
  22. package/dist/main/types/tree-props.d.ts +3 -1
  23. package/dist/module/components/cursor.js +1 -2
  24. package/dist/module/components/default-container.js +32 -3
  25. package/dist/module/components/default-cursor.js +1 -1
  26. package/dist/module/components/default-drag-preview.d.ts +1 -1
  27. package/dist/module/components/default-drag-preview.js +1 -1
  28. package/dist/module/components/default-row.d.ts +1 -1
  29. package/dist/module/components/default-row.js +1 -1
  30. package/dist/module/components/list-outer-element.js +1 -1
  31. package/dist/module/components/provider.d.ts +1 -1
  32. package/dist/module/components/provider.js +4 -4
  33. package/dist/module/components/provider.test.js +71 -1
  34. package/dist/module/components/row-container.d.ts +1 -1
  35. package/dist/module/components/row-container.js +2 -3
  36. package/dist/module/dnd/compute-drop.js +1 -1
  37. package/dist/module/dnd/drag-hook.js +1 -0
  38. package/dist/module/hooks/use-validated-props.js +1 -2
  39. package/dist/module/interfaces/node-api.js +4 -1
  40. package/dist/module/interfaces/tree-api.d.ts +27 -5
  41. package/dist/module/interfaces/tree-api.js +98 -14
  42. package/dist/module/interfaces/tree-api.test.js +31 -0
  43. package/dist/module/state/drag-slice.js +1 -2
  44. package/dist/module/types/state.d.ts +1 -1
  45. package/dist/module/types/tree-props.d.ts +3 -1
  46. package/package.json +27 -27
  47. package/src/components/cursor.tsx +1 -2
  48. package/src/components/default-container.tsx +40 -19
  49. package/src/components/default-cursor.tsx +1 -5
  50. package/src/components/default-drag-preview.tsx +3 -16
  51. package/src/components/default-node.tsx +0 -1
  52. package/src/components/default-row.tsx +2 -13
  53. package/src/components/drag-preview-container.tsx +1 -1
  54. package/src/components/list-inner-element.tsx +1 -1
  55. package/src/components/list-outer-element.tsx +2 -3
  56. package/src/components/provider.test.tsx +85 -9
  57. package/src/components/provider.tsx +8 -23
  58. package/src/components/row-container.tsx +4 -9
  59. package/src/components/tree.tsx +2 -6
  60. package/src/context.ts +2 -3
  61. package/src/data/create-index.ts +0 -1
  62. package/src/data/create-list.ts +1 -2
  63. package/src/data/create-root.ts +2 -9
  64. package/src/data/simple-tree.ts +5 -3
  65. package/src/dnd/compute-drop.ts +6 -15
  66. package/src/dnd/drag-hook.ts +1 -0
  67. package/src/dnd/measure-hover.ts +2 -6
  68. package/src/dnd/outer-drop-hook.ts +1 -1
  69. package/src/hooks/use-fresh-node.ts +0 -1
  70. package/src/hooks/use-simple-tree.ts +2 -8
  71. package/src/hooks/use-validated-props.ts +4 -8
  72. package/src/interfaces/node-api.ts +2 -2
  73. package/src/interfaces/tree-api.test.ts +35 -0
  74. package/src/interfaces/tree-api.ts +103 -36
  75. package/src/state/dnd-slice.ts +1 -1
  76. package/src/state/drag-slice.ts +2 -5
  77. package/src/state/edit-slice.ts +1 -4
  78. package/src/state/focus-slice.ts +1 -1
  79. package/src/state/open-slice.ts +2 -5
  80. package/src/state/selection-slice.ts +2 -6
  81. package/src/types/handlers.ts +1 -3
  82. package/src/types/renderers.ts +0 -1
  83. package/src/types/state.ts +1 -1
  84. package/src/types/tree-props.ts +6 -10
  85. package/src/types/utils.ts +2 -3
  86. package/src/utils.ts +5 -14
@@ -6,6 +6,8 @@ import { ListOnScrollProps, CommonProps as ReactWindowCommonProps } from "react-
6
6
  import { NodeApi } from "../interfaces/node-api";
7
7
  import { OpenMap } from "../state/open-slice";
8
8
  import { useDragDropManager, DndProviderProps } from "react-dnd";
9
+ /** Returns the height in pixels for a given node's row. */
10
+ export type RowHeightAccessor<T> = (node: NodeApi<T>) => number;
9
11
  export interface TreeProps<T> {
10
12
  data?: readonly T[];
11
13
  initialData?: readonly T[];
@@ -18,7 +20,7 @@ export interface TreeProps<T> {
18
20
  renderDragPreview?: ElementType<renderers.DragPreviewProps>;
19
21
  renderCursor?: ElementType<renderers.CursorProps>;
20
22
  renderContainer?: ElementType<{}>;
21
- rowHeight?: number;
23
+ rowHeight?: number | RowHeightAccessor<T>;
22
24
  overscanCount?: number;
23
25
  width?: number | string;
24
26
  height?: number;
package/package.json CHANGED
@@ -1,12 +1,33 @@
1
1
  {
2
2
  "name": "react-arborist",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
+ "keywords": [
5
+ "arborist",
6
+ "dnd",
7
+ "filterable",
8
+ "multiselection",
9
+ "react",
10
+ "react-arborist",
11
+ "tree",
12
+ "treeview",
13
+ "virtualized"
14
+ ],
15
+ "homepage": "https://react-arborist.netlify.app",
16
+ "bugs": "https://github.com/jameskerr/react-arborist/issues",
4
17
  "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/jameskerr/react-arborist.git"
21
+ },
5
22
  "source": "src/index.ts",
23
+ "files": [
24
+ "src",
25
+ "dist"
26
+ ],
27
+ "sideEffects": false,
6
28
  "main": "dist/main/index.js",
7
29
  "module": "dist/module/index.js",
8
30
  "types": "dist/module/index.d.ts",
9
- "sideEffects": false,
10
31
  "scripts": {
11
32
  "build:cjs": "tsc --outDir dist/main",
12
33
  "build:es": "tsc --outDir dist/module --module es2022 --moduleResolution node",
@@ -16,27 +37,6 @@
16
37
  "test": "jest",
17
38
  "watch": "yarn build:es --watch"
18
39
  },
19
- "files": [
20
- "src",
21
- "dist"
22
- ],
23
- "repository": {
24
- "type": "git",
25
- "url": "https://github.com/jameskerr/react-arborist.git"
26
- },
27
- "homepage": "https://react-arborist.netlify.app",
28
- "bugs": "https://github.com/jameskerr/react-arborist/issues",
29
- "keywords": [
30
- "react",
31
- "arborist",
32
- "react-arborist",
33
- "treeview",
34
- "tree",
35
- "vitualized",
36
- "dnd",
37
- "multiselection",
38
- "filterable"
39
- ],
40
40
  "dependencies": {
41
41
  "react-dnd": "^14.0.3",
42
42
  "react-dnd-html5-backend": "^14.0.3",
@@ -44,10 +44,6 @@
44
44
  "redux": "^5.0.0",
45
45
  "use-sync-external-store": "^1.2.0"
46
46
  },
47
- "peerDependencies": {
48
- "react": ">= 16.14",
49
- "react-dom": ">= 16.14"
50
- },
51
47
  "devDependencies": {
52
48
  "@testing-library/dom": "^9.3.0",
53
49
  "@testing-library/react": "^14.0.0",
@@ -64,5 +60,9 @@
64
60
  "rimraf": "^5.0.5",
65
61
  "ts-jest": "^29.1.1",
66
62
  "typescript": "^5.6.0"
63
+ },
64
+ "peerDependencies": {
65
+ "react": ">= 16.14",
66
+ "react-dom": ">= 16.14"
67
67
  }
68
68
  }
@@ -7,8 +7,7 @@ export function Cursor() {
7
7
  if (!cursor || cursor.type !== "line") return null;
8
8
  const indent = tree.indent;
9
9
  const top =
10
- tree.rowHeight * cursor.index +
11
- (tree.props.padding ?? tree.props.paddingTop ?? 0);
10
+ tree.rowTopPosition(cursor.index) + (tree.props.padding ?? tree.props.paddingTop ?? 0);
12
11
  const left = indent * cursor.level;
13
12
  const Cursor = tree.renderCursor;
14
13
  return <Cursor {...{ top, left, indent }} />;
@@ -1,4 +1,4 @@
1
- import { FixedSizeList } from "react-window";
1
+ import { FixedSizeList, VariableSizeList } from "react-window";
2
2
  import { useDataUpdates, useTreeApi } from "../context";
3
3
  import { focusNextElement, focusPrevElement } from "../utils";
4
4
  import { ListOuterElement } from "./list-outer-element";
@@ -216,24 +216,45 @@ export function DefaultContainer() {
216
216
  if (node) tree.focus(node.id);
217
217
  }}
218
218
  >
219
- {/* @ts-ignore */}
220
- <FixedSizeList
221
- className={tree.props.className}
222
- outerRef={tree.listEl}
223
- itemCount={tree.visibleNodes.length}
224
- height={tree.height}
225
- width={tree.width}
226
- itemSize={tree.rowHeight}
227
- overscanCount={tree.overscanCount}
228
- itemKey={(index) => tree.visibleNodes[index]?.id || index}
229
- outerElementType={tree.props.outerElementType ?? ListOuterElement}
230
- innerElementType={tree.props.innerElementType ?? ListInnerElement}
231
- onScroll={tree.props.onScroll}
232
- onItemsRendered={tree.onItemsRendered.bind(tree)}
233
- ref={tree.list}
234
- >
235
- {RowContainer}
236
- </FixedSizeList>
219
+ <List />
237
220
  </div>
238
221
  );
239
222
  }
223
+
224
+ /**
225
+ * Fixed-height trees (numeric rowHeight) render a FixedSizeList, preserving the
226
+ * original O(1) layout and avoiding VariableSizeList's measurement cache. Only
227
+ * the function form, which needs per-row heights, uses VariableSizeList.
228
+ */
229
+ function List() {
230
+ const tree = useTreeApi();
231
+ const commonProps = {
232
+ className: tree.props.className,
233
+ outerRef: tree.listEl,
234
+ itemCount: tree.visibleNodes.length,
235
+ height: tree.height,
236
+ width: tree.width,
237
+ overscanCount: tree.overscanCount,
238
+ itemKey: (index: number) => tree.visibleNodes[index]?.id || index,
239
+ outerElementType: tree.props.outerElementType ?? ListOuterElement,
240
+ innerElementType: tree.props.innerElementType ?? ListInnerElement,
241
+ onScroll: tree.props.onScroll,
242
+ onItemsRendered: tree.onItemsRendered.bind(tree),
243
+ };
244
+
245
+ if (typeof tree.props.rowHeight === "function") {
246
+ return (
247
+ // @ts-ignore
248
+ <VariableSizeList {...commonProps} itemSize={tree.rowHeightAt} ref={tree.list}>
249
+ {RowContainer}
250
+ </VariableSizeList>
251
+ );
252
+ }
253
+
254
+ return (
255
+ // @ts-ignore
256
+ <FixedSizeList {...commonProps} itemSize={tree.rowHeight} ref={tree.list}>
257
+ {RowContainer}
258
+ </FixedSizeList>
259
+ );
260
+ }
@@ -21,11 +21,7 @@ const circleStyle = {
21
21
  borderRadius: "50%",
22
22
  };
23
23
 
24
- export const DefaultCursor = React.memo(function DefaultCursor({
25
- top,
26
- left,
27
- indent,
28
- }: CursorProps) {
24
+ export const DefaultCursor = React.memo(function DefaultCursor({ top, left, indent }: CursorProps) {
29
25
  const style: CSSProperties = {
30
26
  position: "absolute",
31
27
  pointerEvents: "none",
@@ -2,7 +2,6 @@ import React, { CSSProperties, memo } from "react";
2
2
  import { XYCoord } from "react-dnd";
3
3
  import { useTreeApi } from "../context";
4
4
  import { DragPreviewProps } from "../types/renderers";
5
- import { IdObj } from "../types/utils";
6
5
 
7
6
  const layerStyles: CSSProperties = {
8
7
  position: "fixed",
@@ -26,13 +25,7 @@ const getCountStyle = (offset: XYCoord | null) => {
26
25
  return { transform: `translate(${x + 10}px, ${y + 10}px)` };
27
26
  };
28
27
 
29
- export function DefaultDragPreview({
30
- offset,
31
- mouse,
32
- id,
33
- dragIds,
34
- isDragging,
35
- }: DragPreviewProps) {
28
+ export function DefaultDragPreview({ offset, mouse, id, dragIds, isDragging }: DragPreviewProps) {
36
29
  return (
37
30
  <Overlay isDragging={isDragging}>
38
31
  <Position offset={offset}>
@@ -43,10 +36,7 @@ export function DefaultDragPreview({
43
36
  );
44
37
  }
45
38
 
46
- const Overlay = memo(function Overlay(props: {
47
- children: JSX.Element[];
48
- isDragging: boolean;
49
- }) {
39
+ const Overlay = memo(function Overlay(props: { children: JSX.Element[]; isDragging: boolean }) {
50
40
  if (!props.isDragging) return null;
51
41
  return <div style={layerStyles}>{props.children}</div>;
52
42
  });
@@ -70,10 +60,7 @@ function Count(props: { count: number; mouse: XYCoord | null }) {
70
60
  else return null;
71
61
  }
72
62
 
73
- const PreviewNode = memo(function PreviewNode<T>(props: {
74
- id: string | null;
75
- dragIds: string[];
76
- }) {
63
+ const PreviewNode = memo(function PreviewNode<T>(props: { id: string | null; dragIds: string[] }) {
77
64
  const tree = useTreeApi<T>();
78
65
  const node = tree.get(props.id);
79
66
  if (!node) return null;
@@ -1,6 +1,5 @@
1
1
  import React, { useEffect, useRef } from "react";
2
2
  import { NodeRendererProps } from "../types/renderers";
3
- import { IdObj } from "../types/utils";
4
3
 
5
4
  export function DefaultNode<T>(props: NodeRendererProps<T>) {
6
5
  return (
@@ -1,20 +1,9 @@
1
1
  import React from "react";
2
2
  import { RowRendererProps } from "../types/renderers";
3
- import { IdObj } from "../types/utils";
4
3
 
5
- export function DefaultRow<T>({
6
- node,
7
- attrs,
8
- innerRef,
9
- children,
10
- }: RowRendererProps<T>) {
4
+ export function DefaultRow<T>({ node, attrs, innerRef, children }: RowRendererProps<T>) {
11
5
  return (
12
- <div
13
- {...attrs}
14
- ref={innerRef}
15
- onFocus={(e) => e.stopPropagation()}
16
- onClick={node.handleClick}
17
- >
6
+ <div {...attrs} ref={innerRef} onFocus={(e) => e.stopPropagation()} onClick={node.handleClick}>
18
7
  {children}
19
8
  </div>
20
9
  );
@@ -1,5 +1,5 @@
1
1
  import { useDragLayer } from "react-dnd";
2
- import { useDndContext, useTreeApi } from "../context";
2
+ import { useTreeApi } from "../context";
3
3
  import { DefaultDragPreview } from "./default-drag-preview";
4
4
 
5
5
  export function DragPreviewContainer() {
@@ -4,7 +4,7 @@ import { useTreeApi } from "../context";
4
4
 
5
5
  export const ListInnerElement = forwardRef<any, any>(function InnerElement(
6
6
  { style, ...rest },
7
- ref
7
+ ref,
8
8
  ) {
9
9
  const tree = useTreeApi();
10
10
  const paddingTop = tree.props.padding ?? tree.props.paddingTop ?? 0;
@@ -1,11 +1,10 @@
1
1
  import { forwardRef } from "react";
2
2
  import { useTreeApi } from "../context";
3
- import { treeBlur } from "../state/focus-slice";
4
3
  import { Cursor } from "./cursor";
5
4
 
6
5
  export const ListOuterElement = forwardRef(function Outer(
7
6
  props: React.HTMLProps<HTMLDivElement>,
8
- ref
7
+ ref,
9
8
  ) {
10
9
  const { children, ...rest } = props;
11
10
  const tree = useTreeApi();
@@ -29,7 +28,7 @@ export const DropContainer = () => {
29
28
  return (
30
29
  <div
31
30
  style={{
32
- height: tree.visibleNodes.length * tree.rowHeight,
31
+ height: tree.rowTopPosition(tree.visibleNodes.length),
33
32
  width: "100%",
34
33
  position: "absolute",
35
34
  left: "0",
@@ -1,7 +1,9 @@
1
1
  import { createRef } from "react";
2
- import { act, render } from "@testing-library/react";
2
+ import { act, render, screen } from "@testing-library/react";
3
+ import { FixedSizeList, VariableSizeList } from "react-window";
3
4
  import { Tree } from "./tree";
4
5
  import { TreeApi } from "../interfaces/tree-api";
6
+ import { NodeApi } from "../interfaces/node-api";
5
7
 
6
8
  type Datum = { id: string; name: string; children?: Datum[] };
7
9
 
@@ -18,14 +20,7 @@ const data: Datum[] = [
18
20
 
19
21
  test("imperative tree.update() props survive node toggles (#228)", () => {
20
22
  const ref = createRef<TreeApi<Datum> | undefined>();
21
- render(
22
- <Tree<Datum>
23
- data={data}
24
- ref={ref}
25
- rowHeight={24}
26
- openByDefault={false}
27
- />
28
- );
23
+ render(<Tree<Datum> data={data} ref={ref} rowHeight={24} openByDefault={false} />);
29
24
  const api = ref.current!;
30
25
  expect(api.rowHeight).toBe(24);
31
26
 
@@ -42,3 +37,84 @@ test("imperative tree.update() props survive node toggles (#228)", () => {
42
37
  });
43
38
  expect(api.rowHeight).toBe(48);
44
39
  });
40
+
41
+ /* Backwards compatibility: switching FixedSizeList -> VariableSizeList must not
42
+ change layout for a numeric rowHeight. With openByDefault, all four nodes
43
+ (1 > 2, 3 > 4) are visible in DFS order. */
44
+ test("numeric rowHeight positions rows at index * height (#238 back-compat)", () => {
45
+ render(<Tree<Datum> data={data} rowHeight={24} openByDefault />);
46
+ const rows = screen.getAllByRole("treeitem");
47
+ expect(rows).toHaveLength(4);
48
+ rows.forEach((row, i) => {
49
+ expect(row.style.height).toBe("24px");
50
+ expect(row.style.top).toBe(`${i * 24}px`);
51
+ });
52
+ });
53
+
54
+ test("function rowHeight gives each row its own height and cumulative top (#238)", () => {
55
+ const heights: Record<string, number> = { "1": 40, "2": 20, "3": 30, "4": 10 };
56
+ render(<Tree<Datum> data={data} rowHeight={(node) => heights[node.id]} openByDefault />);
57
+ const rows = screen.getAllByRole("treeitem");
58
+ expect(rows).toHaveLength(4);
59
+ const expected = [40, 20, 30, 10];
60
+ let top = 0;
61
+ rows.forEach((row, i) => {
62
+ expect(row.style.height).toBe(`${expected[i]}px`);
63
+ expect(row.style.top).toBe(`${top}px`);
64
+ top += expected[i];
65
+ });
66
+ });
67
+
68
+ test("mutations tell the list to recompute heights (#238)", () => {
69
+ const ref = createRef<TreeApi<Datum> | undefined>();
70
+ /* Only variable-height mode renders a VariableSizeList with a measurement
71
+ cache to recompute, so use a function rowHeight here. */
72
+ render(<Tree<Datum> data={data} ref={ref} rowHeight={() => 24} openByDefault />);
73
+ const api = ref.current!;
74
+ const reset = jest.spyOn(api.list.current as VariableSizeList, "resetAfterIndex");
75
+
76
+ act(() => api.close("1"));
77
+ expect(reset).toHaveBeenCalled();
78
+
79
+ reset.mockClear();
80
+ act(() => api.open("1"));
81
+ expect(reset).toHaveBeenCalled();
82
+ });
83
+
84
+ /* react-window caches measurements by index and never invalidates them itself.
85
+ When data changes via props in variable-height mode, those cached sizes belong
86
+ to the wrong rows, so update() must drop the cache. It runs during render, so
87
+ it uses the shouldForceUpdate=false variant. */
88
+ test("changing data in variable-height mode resets the list cache (#238)", () => {
89
+ const ref = createRef<TreeApi<Datum> | undefined>();
90
+ const rowHeight = (node: NodeApi<Datum>) => (node.isInternal ? 40 : 20);
91
+ const { rerender } = render(
92
+ <Tree<Datum> data={data} ref={ref} rowHeight={rowHeight} openByDefault />,
93
+ );
94
+ const reset = jest.spyOn(ref.current!.list.current as VariableSizeList, "resetAfterIndex");
95
+
96
+ const nextData: Datum[] = [{ id: "9", name: "fresh" }, ...data];
97
+ act(() => {
98
+ rerender(<Tree<Datum> data={nextData} ref={ref} rowHeight={rowHeight} openByDefault />);
99
+ });
100
+
101
+ expect(reset).toHaveBeenCalledWith(0, false);
102
+ });
103
+
104
+ /* The numeric path must stay on FixedSizeList: it has constant item sizes, so
105
+ there is no measurement cache to go stale and none of VariableSizeList's
106
+ overhead. A FixedSizeList has no resetAfterIndex method at all. */
107
+ test("numeric rowHeight renders a cache-free FixedSizeList (#238)", () => {
108
+ const ref = createRef<TreeApi<Datum> | undefined>();
109
+ render(<Tree<Datum> data={data} ref={ref} rowHeight={24} openByDefault />);
110
+ const list = ref.current!.list.current!;
111
+ expect(list).toBeInstanceOf(FixedSizeList);
112
+ expect("resetAfterIndex" in list).toBe(false);
113
+ });
114
+
115
+ /* The function path uses VariableSizeList so per-row heights are possible. */
116
+ test("function rowHeight renders a VariableSizeList (#238)", () => {
117
+ const ref = createRef<TreeApi<Datum> | undefined>();
118
+ render(<Tree<Datum> data={data} ref={ref} rowHeight={() => 24} openByDefault />);
119
+ expect(ref.current!.list.current!).toBeInstanceOf(VariableSizeList);
120
+ });
@@ -1,18 +1,7 @@
1
- import {
2
- ReactNode,
3
- useEffect,
4
- useImperativeHandle,
5
- useMemo,
6
- useRef,
7
- } from "react";
1
+ import { ReactNode, useEffect, useImperativeHandle, useMemo, useRef } from "react";
8
2
  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";
3
+ import { FixedSizeList, VariableSizeList } from "react-window";
4
+ import { DataUpdatesContext, DndContext, NodesContext, TreeApiContext } from "../context";
16
5
  import { TreeApi } from "../interfaces/tree-api";
17
6
  import { initialState } from "../state/initial";
18
7
  import { Actions, rootReducer, RootState } from "../state/root-reducer";
@@ -30,21 +19,17 @@ type Props<T> = {
30
19
 
31
20
  const SERVER_STATE = initialState();
32
21
 
33
- export function TreeProvider<T>({
34
- treeProps,
35
- imperativeHandle,
36
- children,
37
- }: Props<T>) {
38
- const list = useRef<FixedSizeList | null>(null);
22
+ export function TreeProvider<T>({ treeProps, imperativeHandle, children }: Props<T>) {
23
+ const list = useRef<FixedSizeList | VariableSizeList | null>(null);
39
24
  const listEl = useRef<HTMLDivElement | null>(null);
40
25
  const store = useRef<Store<RootState, Actions>>(
41
26
  // @ts-ignore
42
- createStore(rootReducer, initialState(treeProps))
27
+ createStore(rootReducer, initialState(treeProps)),
43
28
  );
44
29
  const state = useSyncExternalStore<RootState>(
45
30
  store.current.subscribe,
46
31
  store.current.getState,
47
- () => SERVER_STATE
32
+ () => SERVER_STATE,
48
33
  );
49
34
 
50
35
  /* The tree api object is stable. */
@@ -57,7 +42,7 @@ export function TreeProvider<T>({
57
42
  useMemo(() => {
58
43
  updateCount.current += 1;
59
44
  api.update(treeProps);
60
- }, [...Object.values(treeProps)]);
45
+ }, Object.values(treeProps));
61
46
 
62
47
  /* Rebuild visible nodes when open state changes, without clobbering
63
48
  props set imperatively via api.update(). Bumping updateCount keeps
@@ -9,10 +9,7 @@ type Props = {
9
9
  index: number;
10
10
  };
11
11
 
12
- export const RowContainer = React.memo(function RowContainer<T>({
13
- index,
14
- style,
15
- }: Props) {
12
+ export const RowContainer = React.memo(function RowContainer<T>({ index, style }: Props) {
16
13
  /* When will the <Row> will re-render.
17
14
  *
18
15
  * The row component is memo'd so it will only render
@@ -42,7 +39,7 @@ export const RowContainer = React.memo(function RowContainer<T>({
42
39
  el.current = n;
43
40
  dropRef(n);
44
41
  },
45
- [dropRef]
42
+ [dropRef],
46
43
  );
47
44
 
48
45
  const indent = tree.indent * node.level;
@@ -50,11 +47,9 @@ export const RowContainer = React.memo(function RowContainer<T>({
50
47
  const rowStyle = useMemo(
51
48
  () => ({
52
49
  ...style,
53
- top:
54
- parseFloat(style.top as string) +
55
- (tree.props.padding ?? tree.props.paddingTop ?? 0),
50
+ top: parseFloat(style.top as string) + (tree.props.padding ?? tree.props.paddingTop ?? 0),
56
51
  }),
57
- [style, tree.props.padding, tree.props.paddingTop]
52
+ [style, tree.props.padding, tree.props.paddingTop],
58
53
  );
59
54
  const rowAttrs: React.HTMLAttributes<any> = {
60
55
  role: "treeitem",
@@ -5,13 +5,9 @@ import { OuterDrop } from "./outer-drop";
5
5
  import { TreeContainer } from "./tree-container";
6
6
  import { DragPreviewContainer } from "./drag-preview-container";
7
7
  import { TreeProps } from "../types/tree-props";
8
- import { IdObj } from "../types/utils";
9
8
  import { useValidatedProps } from "../hooks/use-validated-props";
10
9
 
11
- function TreeComponent<T>(
12
- props: TreeProps<T>,
13
- ref: React.Ref<TreeApi<T> | undefined>
14
- ) {
10
+ function TreeComponent<T>(props: TreeProps<T>, ref: React.Ref<TreeApi<T> | undefined>) {
15
11
  const treeProps = useValidatedProps(props);
16
12
  return (
17
13
  <TreeProvider treeProps={treeProps} imperativeHandle={ref}>
@@ -24,5 +20,5 @@ function TreeComponent<T>(
24
20
  }
25
21
 
26
22
  export const Tree = forwardRef(TreeComponent) as <T>(
27
- props: TreeProps<T> & { ref?: React.ForwardedRef<TreeApi<T> | undefined> }
23
+ props: TreeProps<T> & { ref?: React.ForwardedRef<TreeApi<T> | undefined> },
28
24
  ) => ReturnType<typeof TreeComponent>;
package/src/context.ts CHANGED
@@ -1,13 +1,12 @@
1
- import React, { createContext, useContext, useMemo } from "react";
1
+ import React, { createContext, useContext } from "react";
2
2
  import { TreeApi } from "./interfaces/tree-api";
3
3
  import { RootState } from "./state/root-reducer";
4
- import { IdObj } from "./types/utils";
5
4
 
6
5
  export const TreeApiContext = createContext<TreeApi<any> | null>(null);
7
6
 
8
7
  export function useTreeApi<T>() {
9
8
  const value = useContext<TreeApi<T> | null>(
10
- TreeApiContext as unknown as React.Context<TreeApi<T> | null>
9
+ TreeApiContext as unknown as React.Context<TreeApi<T> | null>,
11
10
  );
12
11
  if (value === null) throw new Error("No Tree Api Provided");
13
12
  return value;
@@ -1,5 +1,4 @@
1
1
  import { NodeApi } from "../interfaces/node-api";
2
- import { IdObj } from "../types/utils";
3
2
 
4
3
  export const createIndex = <T>(nodes: NodeApi<T>[]) => {
5
4
  return nodes.reduce<{ [id: string]: number }>((map, node, index) => {
@@ -1,6 +1,5 @@
1
1
  import { NodeApi } from "../interfaces/node-api";
2
2
  import { TreeApi } from "../interfaces/tree-api";
3
- import { IdObj } from "../types/utils";
4
3
 
5
4
  export function createList<T>(tree: TreeApi<T>) {
6
5
  if (tree.isFiltered) {
@@ -27,7 +26,7 @@ function flattenTree<T>(root: NodeApi<T>): NodeApi<T>[] {
27
26
 
28
27
  function flattenAndFilterTree<T>(
29
28
  root: NodeApi<T>,
30
- isMatch: (n: NodeApi<T>) => boolean
29
+ isMatch: (n: NodeApi<T>) => boolean,
31
30
  ): NodeApi<T>[] {
32
31
  const matches: Record<string, boolean> = {};
33
32
  const list: NodeApi<T>[] = [];
@@ -1,15 +1,10 @@
1
- import { IdObj } from "../types/utils";
2
1
  import { NodeApi } from "../interfaces/node-api";
3
2
  import { TreeApi } from "../interfaces/tree-api";
4
3
 
5
4
  export const ROOT_ID = "__REACT_ARBORIST_INTERNAL_ROOT__";
6
5
 
7
6
  export function createRoot<T>(tree: TreeApi<T>): NodeApi<T> {
8
- function visitSelfAndChildren(
9
- data: T,
10
- level: number,
11
- parent: NodeApi<T> | null
12
- ) {
7
+ function visitSelfAndChildren(data: T, level: number, parent: NodeApi<T> | null) {
13
8
  const id = tree.accessId(data);
14
9
  const node = new NodeApi<T>({
15
10
  tree,
@@ -23,9 +18,7 @@ export function createRoot<T>(tree: TreeApi<T>): NodeApi<T> {
23
18
  });
24
19
  const children = tree.accessChildren(data);
25
20
  if (children) {
26
- node.children = children.map((child: T) =>
27
- visitSelfAndChildren(child, level + 1, node)
28
- );
21
+ node.children = children.map((child: T) => visitSelfAndChildren(child, level + 1, node));
29
22
  }
30
23
  return node;
31
24
  }
@@ -56,15 +56,17 @@ function createRoot<T extends SimpleData>(data: T[]) {
56
56
 
57
57
  function createNode<T extends SimpleData>(data: T, parent: SimpleNode<T>) {
58
58
  const node = new SimpleNode<T>(data, parent);
59
- if (data.children)
60
- node.children = data.children.map((d) => createNode<T>(d as T, node));
59
+ if (data.children) node.children = data.children.map((d) => createNode<T>(d as T, node));
61
60
  return node;
62
61
  }
63
62
 
64
63
  class SimpleNode<T extends SimpleData> {
65
64
  id: string;
66
65
  children?: SimpleNode<T>[];
67
- constructor(public data: T, public parent: SimpleNode<T> | null) {
66
+ constructor(
67
+ public data: T,
68
+ public parent: SimpleNode<T> | null,
69
+ ) {
68
70
  this.id = data.id;
69
71
  }
70
72