react-arborist 3.7.0 → 3.9.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 (97) hide show
  1. package/README.md +14 -0
  2. package/dist/main/components/cursor.js +1 -2
  3. package/dist/main/components/default-container.js +31 -2
  4. package/dist/main/components/default-cursor.js +1 -1
  5. package/dist/main/components/default-drag-preview.d.ts +1 -1
  6. package/dist/main/components/default-drag-preview.js +1 -1
  7. package/dist/main/components/default-row.d.ts +1 -1
  8. package/dist/main/components/default-row.js +1 -1
  9. package/dist/main/components/list-outer-element.js +1 -1
  10. package/dist/main/components/provider.d.ts +1 -1
  11. package/dist/main/components/provider.js +2 -2
  12. package/dist/main/components/provider.test.js +70 -0
  13. package/dist/main/components/row-container.d.ts +1 -1
  14. package/dist/main/components/row-container.js +2 -3
  15. package/dist/main/dnd/drag-hook.d.ts +2 -0
  16. package/dist/main/dnd/drag-hook.js +13 -4
  17. package/dist/main/dnd/drag-hook.test.d.ts +1 -0
  18. package/dist/main/dnd/drag-hook.test.js +19 -0
  19. package/dist/main/hooks/use-validated-props.js +1 -2
  20. package/dist/main/interfaces/node-api.js +4 -1
  21. package/dist/main/interfaces/tree-api.d.ts +27 -5
  22. package/dist/main/interfaces/tree-api.js +98 -14
  23. package/dist/main/interfaces/tree-api.test.js +31 -0
  24. package/dist/main/state/drag-slice.js +1 -2
  25. package/dist/main/types/dnd.d.ts +3 -1
  26. package/dist/main/types/state.d.ts +1 -1
  27. package/dist/main/types/tree-props.d.ts +4 -1
  28. package/dist/module/components/cursor.js +1 -2
  29. package/dist/module/components/default-container.js +32 -3
  30. package/dist/module/components/default-cursor.js +1 -1
  31. package/dist/module/components/default-drag-preview.d.ts +1 -1
  32. package/dist/module/components/default-drag-preview.js +1 -1
  33. package/dist/module/components/default-row.d.ts +1 -1
  34. package/dist/module/components/default-row.js +1 -1
  35. package/dist/module/components/list-outer-element.js +1 -1
  36. package/dist/module/components/provider.d.ts +1 -1
  37. package/dist/module/components/provider.js +4 -4
  38. package/dist/module/components/provider.test.js +71 -1
  39. package/dist/module/components/row-container.d.ts +1 -1
  40. package/dist/module/components/row-container.js +2 -3
  41. package/dist/module/dnd/compute-drop.js +1 -1
  42. package/dist/module/dnd/drag-hook.d.ts +2 -0
  43. package/dist/module/dnd/drag-hook.js +12 -4
  44. package/dist/module/dnd/drag-hook.test.d.ts +1 -0
  45. package/dist/module/dnd/drag-hook.test.js +17 -0
  46. package/dist/module/hooks/use-validated-props.js +1 -2
  47. package/dist/module/interfaces/node-api.js +4 -1
  48. package/dist/module/interfaces/tree-api.d.ts +27 -5
  49. package/dist/module/interfaces/tree-api.js +98 -14
  50. package/dist/module/interfaces/tree-api.test.js +31 -0
  51. package/dist/module/state/drag-slice.js +1 -2
  52. package/dist/module/types/dnd.d.ts +3 -1
  53. package/dist/module/types/state.d.ts +1 -1
  54. package/dist/module/types/tree-props.d.ts +4 -1
  55. package/package.json +27 -27
  56. package/src/components/cursor.tsx +1 -2
  57. package/src/components/default-container.tsx +40 -19
  58. package/src/components/default-cursor.tsx +1 -5
  59. package/src/components/default-drag-preview.tsx +3 -16
  60. package/src/components/default-node.tsx +0 -1
  61. package/src/components/default-row.tsx +2 -13
  62. package/src/components/drag-preview-container.tsx +1 -1
  63. package/src/components/list-inner-element.tsx +1 -1
  64. package/src/components/list-outer-element.tsx +2 -3
  65. package/src/components/provider.test.tsx +85 -9
  66. package/src/components/provider.tsx +8 -23
  67. package/src/components/row-container.tsx +4 -9
  68. package/src/components/tree.tsx +2 -6
  69. package/src/context.ts +2 -3
  70. package/src/data/create-index.ts +0 -1
  71. package/src/data/create-list.ts +1 -2
  72. package/src/data/create-root.ts +2 -9
  73. package/src/data/simple-tree.ts +5 -3
  74. package/src/dnd/compute-drop.ts +6 -15
  75. package/src/dnd/drag-hook.test.ts +22 -0
  76. package/src/dnd/drag-hook.ts +15 -6
  77. package/src/dnd/measure-hover.ts +2 -6
  78. package/src/dnd/outer-drop-hook.ts +1 -1
  79. package/src/hooks/use-fresh-node.ts +0 -1
  80. package/src/hooks/use-simple-tree.ts +2 -8
  81. package/src/hooks/use-validated-props.ts +4 -8
  82. package/src/interfaces/node-api.ts +2 -2
  83. package/src/interfaces/tree-api.test.ts +35 -0
  84. package/src/interfaces/tree-api.ts +103 -36
  85. package/src/state/dnd-slice.ts +1 -1
  86. package/src/state/drag-slice.ts +2 -5
  87. package/src/state/edit-slice.ts +1 -4
  88. package/src/state/focus-slice.ts +1 -1
  89. package/src/state/open-slice.ts +2 -5
  90. package/src/state/selection-slice.ts +2 -6
  91. package/src/types/dnd.ts +6 -1
  92. package/src/types/handlers.ts +1 -3
  93. package/src/types/renderers.ts +0 -1
  94. package/src/types/state.ts +1 -1
  95. package/src/types/tree-props.ts +15 -10
  96. package/src/types/utils.ts +2 -3
  97. package/src/utils.ts +5 -14
package/README.md CHANGED
@@ -351,9 +351,23 @@ interface TreeProps<T> {
351
351
  { backend: unknown }
352
352
  >["backend"];
353
353
  dndManager?: ReturnType<typeof useDragDropManager>;
354
+ dragType?: string | ((node: NodeApi<T>) => string);
354
355
  }
355
356
  ```
356
357
 
358
+ ### Dragging Nodes to External Drop Targets
359
+
360
+ The react-dnd drag item created for each row carries the dragged node's `data`, so a drop target rendered outside the tree can read it:
361
+
362
+ ```tsx
363
+ const [, drop] = useDrop(() => ({
364
+ accept: "NODE",
365
+ drop: (item) => console.log(item.data), // the dragged node's data
366
+ }));
367
+ ```
368
+
369
+ The tree and your external target must share one react-dnd backend. Wrap both in a single `DndProvider` and pass its manager to the tree via the `dndManager` prop. By default rows advertise the `"NODE"` item type; set the `dragType` prop (a fixed string, or a function of the node) to advertise a custom type instead. Note that the tree's own drop targets only accept `"NODE"`, so a row given a custom `dragType` is no longer reorderable within the tree.
370
+
357
371
  ## Row Component Props
358
372
 
359
373
  The _\<RowRenderer\>_ is responsible for attaching the drop ref, the row style (top, height) and the aria-attributes. The default should work fine for most use cases, but it can be replaced by your own component if you need. See the _renderRow_ prop in the _\<Tree\>_ component.
@@ -11,8 +11,7 @@ function Cursor() {
11
11
  if (!cursor || cursor.type !== "line")
12
12
  return null;
13
13
  const indent = tree.indent;
14
- const top = tree.rowHeight * cursor.index +
15
- ((_b = (_a = tree.props.padding) !== null && _a !== void 0 ? _a : tree.props.paddingTop) !== null && _b !== void 0 ? _b : 0);
14
+ const top = tree.rowTopPosition(cursor.index) + ((_b = (_a = tree.props.padding) !== null && _a !== void 0 ? _a : tree.props.paddingTop) !== null && _b !== void 0 ? _b : 0);
16
15
  const left = indent * cursor.level;
17
16
  const Cursor = tree.renderCursor;
18
17
  return (0, jsx_runtime_1.jsx)(Cursor, { top, left, indent });
@@ -16,7 +16,6 @@ let timeoutId = null;
16
16
  * the event handler. Future clean up welcome.
17
17
  */
18
18
  function DefaultContainer() {
19
- var _a, _b;
20
19
  (0, context_1.useDataUpdates)();
21
20
  const tree = (0, context_1.useTreeApi)();
22
21
  return ((0, jsx_runtime_1.jsx)("div", { role: "tree", style: {
@@ -234,5 +233,35 @@ function DefaultContainer() {
234
233
  });
235
234
  if (node)
236
235
  tree.focus(node.id);
237
- }, children: (0, jsx_runtime_1.jsx)(react_window_1.FixedSizeList, { className: tree.props.className, outerRef: tree.listEl, itemCount: tree.visibleNodes.length, height: tree.height, width: tree.width, itemSize: tree.rowHeight, overscanCount: tree.overscanCount, itemKey: (index) => { var _a; return ((_a = tree.visibleNodes[index]) === null || _a === void 0 ? void 0 : _a.id) || index; }, outerElementType: (_a = tree.props.outerElementType) !== null && _a !== void 0 ? _a : list_outer_element_1.ListOuterElement, innerElementType: (_b = tree.props.innerElementType) !== null && _b !== void 0 ? _b : list_inner_element_1.ListInnerElement, onScroll: tree.props.onScroll, onItemsRendered: tree.onItemsRendered.bind(tree), ref: tree.list, children: row_container_1.RowContainer }) }));
236
+ }, children: (0, jsx_runtime_1.jsx)(List, {}) }));
237
+ }
238
+ /**
239
+ * Fixed-height trees (numeric rowHeight) render a FixedSizeList, preserving the
240
+ * original O(1) layout and avoiding VariableSizeList's measurement cache. Only
241
+ * the function form, which needs per-row heights, uses VariableSizeList.
242
+ */
243
+ function List() {
244
+ var _a, _b;
245
+ const tree = (0, context_1.useTreeApi)();
246
+ const commonProps = {
247
+ className: tree.props.className,
248
+ outerRef: tree.listEl,
249
+ itemCount: tree.visibleNodes.length,
250
+ height: tree.height,
251
+ width: tree.width,
252
+ overscanCount: tree.overscanCount,
253
+ itemKey: (index) => { var _a; return ((_a = tree.visibleNodes[index]) === null || _a === void 0 ? void 0 : _a.id) || index; },
254
+ outerElementType: (_a = tree.props.outerElementType) !== null && _a !== void 0 ? _a : list_outer_element_1.ListOuterElement,
255
+ innerElementType: (_b = tree.props.innerElementType) !== null && _b !== void 0 ? _b : list_inner_element_1.ListInnerElement,
256
+ onScroll: tree.props.onScroll,
257
+ onItemsRendered: tree.onItemsRendered.bind(tree),
258
+ };
259
+ if (typeof tree.props.rowHeight === "function") {
260
+ return (
261
+ // @ts-ignore
262
+ (0, jsx_runtime_1.jsx)(react_window_1.VariableSizeList, Object.assign({}, commonProps, { itemSize: tree.rowHeightAt, ref: tree.list, children: row_container_1.RowContainer })));
263
+ }
264
+ return (
265
+ // @ts-ignore
266
+ (0, jsx_runtime_1.jsx)(react_window_1.FixedSizeList, Object.assign({}, commonProps, { itemSize: tree.rowHeight, ref: tree.list, children: row_container_1.RowContainer })));
238
267
  }
@@ -23,7 +23,7 @@ const circleStyle = {
23
23
  boxShadow: "0 0 0 3px #4B91E2",
24
24
  borderRadius: "50%",
25
25
  };
26
- exports.DefaultCursor = react_1.default.memo(function DefaultCursor({ top, left, indent, }) {
26
+ exports.DefaultCursor = react_1.default.memo(function DefaultCursor({ top, left, indent }) {
27
27
  const style = {
28
28
  position: "absolute",
29
29
  pointerEvents: "none",
@@ -1,2 +1,2 @@
1
1
  import { DragPreviewProps } from "../types/renderers";
2
- export declare function DefaultDragPreview({ offset, mouse, id, dragIds, isDragging, }: DragPreviewProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function DefaultDragPreview({ offset, mouse, id, dragIds, isDragging }: DragPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -25,7 +25,7 @@ const getCountStyle = (offset) => {
25
25
  const { x, y } = offset;
26
26
  return { transform: `translate(${x + 10}px, ${y + 10}px)` };
27
27
  };
28
- function DefaultDragPreview({ offset, mouse, id, dragIds, isDragging, }) {
28
+ function DefaultDragPreview({ offset, mouse, id, dragIds, isDragging }) {
29
29
  return ((0, jsx_runtime_1.jsxs)(Overlay, { isDragging: isDragging, children: [(0, jsx_runtime_1.jsx)(Position, { offset: offset, children: (0, jsx_runtime_1.jsx)(PreviewNode, { id: id, dragIds: dragIds }) }), (0, jsx_runtime_1.jsx)(Count, { mouse: mouse, count: dragIds.length })] }));
30
30
  }
31
31
  const Overlay = (0, react_1.memo)(function Overlay(props) {
@@ -1,2 +1,2 @@
1
1
  import { RowRendererProps } from "../types/renderers";
2
- export declare function DefaultRow<T>({ node, attrs, innerRef, children, }: RowRendererProps<T>): import("react/jsx-runtime").JSX.Element;
2
+ export declare function DefaultRow<T>({ node, attrs, innerRef, children }: RowRendererProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -2,6 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DefaultRow = DefaultRow;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
- function DefaultRow({ node, attrs, innerRef, children, }) {
5
+ function DefaultRow({ node, attrs, innerRef, children }) {
6
6
  return ((0, jsx_runtime_1.jsx)("div", Object.assign({}, attrs, { ref: innerRef, onFocus: (e) => e.stopPropagation(), onClick: node.handleClick, children: children })));
7
7
  }
@@ -29,7 +29,7 @@ exports.ListOuterElement = (0, react_1.forwardRef)(function Outer(props, ref) {
29
29
  const DropContainer = () => {
30
30
  const tree = (0, context_1.useTreeApi)();
31
31
  return ((0, jsx_runtime_1.jsx)("div", { style: {
32
- height: tree.visibleNodes.length * tree.rowHeight,
32
+ height: tree.rowTopPosition(tree.visibleNodes.length),
33
33
  width: "100%",
34
34
  position: "absolute",
35
35
  left: "0",
@@ -6,5 +6,5 @@ type Props<T> = {
6
6
  imperativeHandle: React.Ref<TreeApi<T> | undefined>;
7
7
  children: ReactNode;
8
8
  };
9
- export declare function TreeProvider<T>({ treeProps, imperativeHandle, children, }: Props<T>): import("react/jsx-runtime").JSX.Element;
9
+ export declare function TreeProvider<T>({ treeProps, imperativeHandle, children }: Props<T>): import("react/jsx-runtime").JSX.Element;
10
10
  export {};
@@ -13,7 +13,7 @@ const react_dnd_1 = require("react-dnd");
13
13
  const redux_1 = require("redux");
14
14
  const open_slice_1 = require("../state/open-slice");
15
15
  const SERVER_STATE = (0, initial_1.initialState)();
16
- function TreeProvider({ treeProps, imperativeHandle, children, }) {
16
+ function TreeProvider({ treeProps, imperativeHandle, children }) {
17
17
  const list = (0, react_1.useRef)(null);
18
18
  const listEl = (0, react_1.useRef)(null);
19
19
  const store = (0, react_1.useRef)(
@@ -29,7 +29,7 @@ function TreeProvider({ treeProps, imperativeHandle, children, }) {
29
29
  (0, react_1.useMemo)(() => {
30
30
  updateCount.current += 1;
31
31
  api.update(treeProps);
32
- }, [...Object.values(treeProps)]);
32
+ }, Object.values(treeProps));
33
33
  /* Rebuild visible nodes when open state changes, without clobbering
34
34
  props set imperatively via api.update(). Bumping updateCount keeps
35
35
  DataUpdates consumers (e.g. DefaultContainer) in sync. */
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const jsx_runtime_1 = require("react/jsx-runtime");
4
4
  const react_1 = require("react");
5
5
  const react_2 = require("@testing-library/react");
6
+ const react_window_1 = require("react-window");
6
7
  const tree_1 = require("./tree");
7
8
  const data = [
8
9
  {
@@ -31,3 +32,72 @@ test("imperative tree.update() props survive node toggles (#228)", () => {
31
32
  });
32
33
  expect(api.rowHeight).toBe(48);
33
34
  });
35
+ /* Backwards compatibility: switching FixedSizeList -> VariableSizeList must not
36
+ change layout for a numeric rowHeight. With openByDefault, all four nodes
37
+ (1 > 2, 3 > 4) are visible in DFS order. */
38
+ test("numeric rowHeight positions rows at index * height (#238 back-compat)", () => {
39
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(tree_1.Tree, { data: data, rowHeight: 24, openByDefault: true }));
40
+ const rows = react_2.screen.getAllByRole("treeitem");
41
+ expect(rows).toHaveLength(4);
42
+ rows.forEach((row, i) => {
43
+ expect(row.style.height).toBe("24px");
44
+ expect(row.style.top).toBe(`${i * 24}px`);
45
+ });
46
+ });
47
+ test("function rowHeight gives each row its own height and cumulative top (#238)", () => {
48
+ const heights = { "1": 40, "2": 20, "3": 30, "4": 10 };
49
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(tree_1.Tree, { data: data, rowHeight: (node) => heights[node.id], openByDefault: true }));
50
+ const rows = react_2.screen.getAllByRole("treeitem");
51
+ expect(rows).toHaveLength(4);
52
+ const expected = [40, 20, 30, 10];
53
+ let top = 0;
54
+ rows.forEach((row, i) => {
55
+ expect(row.style.height).toBe(`${expected[i]}px`);
56
+ expect(row.style.top).toBe(`${top}px`);
57
+ top += expected[i];
58
+ });
59
+ });
60
+ test("mutations tell the list to recompute heights (#238)", () => {
61
+ const ref = (0, react_1.createRef)();
62
+ /* Only variable-height mode renders a VariableSizeList with a measurement
63
+ cache to recompute, so use a function rowHeight here. */
64
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(tree_1.Tree, { data: data, ref: ref, rowHeight: () => 24, openByDefault: true }));
65
+ const api = ref.current;
66
+ const reset = jest.spyOn(api.list.current, "resetAfterIndex");
67
+ (0, react_2.act)(() => api.close("1"));
68
+ expect(reset).toHaveBeenCalled();
69
+ reset.mockClear();
70
+ (0, react_2.act)(() => api.open("1"));
71
+ expect(reset).toHaveBeenCalled();
72
+ });
73
+ /* react-window caches measurements by index and never invalidates them itself.
74
+ When data changes via props in variable-height mode, those cached sizes belong
75
+ to the wrong rows, so update() must drop the cache. It runs during render, so
76
+ it uses the shouldForceUpdate=false variant. */
77
+ test("changing data in variable-height mode resets the list cache (#238)", () => {
78
+ const ref = (0, react_1.createRef)();
79
+ const rowHeight = (node) => (node.isInternal ? 40 : 20);
80
+ const { rerender } = (0, react_2.render)((0, jsx_runtime_1.jsx)(tree_1.Tree, { data: data, ref: ref, rowHeight: rowHeight, openByDefault: true }));
81
+ const reset = jest.spyOn(ref.current.list.current, "resetAfterIndex");
82
+ const nextData = [{ id: "9", name: "fresh" }, ...data];
83
+ (0, react_2.act)(() => {
84
+ rerender((0, jsx_runtime_1.jsx)(tree_1.Tree, { data: nextData, ref: ref, rowHeight: rowHeight, openByDefault: true }));
85
+ });
86
+ expect(reset).toHaveBeenCalledWith(0, false);
87
+ });
88
+ /* The numeric path must stay on FixedSizeList: it has constant item sizes, so
89
+ there is no measurement cache to go stale and none of VariableSizeList's
90
+ overhead. A FixedSizeList has no resetAfterIndex method at all. */
91
+ test("numeric rowHeight renders a cache-free FixedSizeList (#238)", () => {
92
+ const ref = (0, react_1.createRef)();
93
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(tree_1.Tree, { data: data, ref: ref, rowHeight: 24, openByDefault: true }));
94
+ const list = ref.current.list.current;
95
+ expect(list).toBeInstanceOf(react_window_1.FixedSizeList);
96
+ expect("resetAfterIndex" in list).toBe(false);
97
+ });
98
+ /* The function path uses VariableSizeList so per-row heights are possible. */
99
+ test("function rowHeight renders a VariableSizeList (#238)", () => {
100
+ const ref = (0, react_1.createRef)();
101
+ (0, react_2.render)((0, jsx_runtime_1.jsx)(tree_1.Tree, { data: data, ref: ref, rowHeight: () => 24, openByDefault: true }));
102
+ expect(ref.current.list.current).toBeInstanceOf(react_window_1.VariableSizeList);
103
+ });
@@ -3,5 +3,5 @@ type Props = {
3
3
  style: React.CSSProperties;
4
4
  index: number;
5
5
  };
6
- export declare const RowContainer: React.MemoExoticComponent<(<T>({ index, style, }: Props) => import("react/jsx-runtime").JSX.Element)>;
6
+ export declare const RowContainer: React.MemoExoticComponent<(<T>({ index, style }: Props) => import("react/jsx-runtime").JSX.Element)>;
7
7
  export {};
@@ -30,7 +30,7 @@ const context_1 = require("../context");
30
30
  const drag_hook_1 = require("../dnd/drag-hook");
31
31
  const drop_hook_1 = require("../dnd/drop-hook");
32
32
  const use_fresh_node_1 = require("../hooks/use-fresh-node");
33
- exports.RowContainer = react_1.default.memo(function RowContainer({ index, style, }) {
33
+ exports.RowContainer = react_1.default.memo(function RowContainer({ index, style }) {
34
34
  /* When will the <Row> will re-render.
35
35
  *
36
36
  * The row component is memo'd so it will only render
@@ -61,8 +61,7 @@ exports.RowContainer = react_1.default.memo(function RowContainer({ index, style
61
61
  const nodeStyle = (0, react_1.useMemo)(() => ({ paddingLeft: indent }), [indent]);
62
62
  const rowStyle = (0, react_1.useMemo)(() => {
63
63
  var _a, _b;
64
- return (Object.assign(Object.assign({}, style), { top: parseFloat(style.top) +
65
- ((_b = (_a = tree.props.padding) !== null && _a !== void 0 ? _a : tree.props.paddingTop) !== null && _b !== void 0 ? _b : 0) }));
64
+ return (Object.assign(Object.assign({}, style), { top: parseFloat(style.top) + ((_b = (_a = tree.props.padding) !== null && _a !== void 0 ? _a : tree.props.paddingTop) !== null && _b !== void 0 ? _b : 0) }));
66
65
  }, [style, tree.props.padding, tree.props.paddingTop]);
67
66
  const rowAttrs = {
68
67
  role: "treeitem",
@@ -1,3 +1,5 @@
1
1
  import { ConnectDragSource } from "react-dnd";
2
2
  import { NodeApi } from "../interfaces/node-api";
3
+ import { TreeProps } from "../types/tree-props";
4
+ export declare function dragTypeForNode<T>(dragType: TreeProps<T>["dragType"], node: NodeApi<T>): string;
3
5
  export declare function useDragHook<T>(node: NodeApi<T>): ConnectDragSource;
@@ -1,28 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dragTypeForNode = dragTypeForNode;
3
4
  exports.useDragHook = useDragHook;
4
5
  const react_1 = require("react");
5
6
  const react_dnd_1 = require("react-dnd");
6
7
  const react_dnd_html5_backend_1 = require("react-dnd-html5-backend");
7
8
  const context_1 = require("../context");
8
9
  const dnd_slice_1 = require("../state/dnd-slice");
10
+ /* The react-dnd item type a row's drag source broadcasts. The dragType prop
11
+ can be a fixed string or a per-node function; it defaults to "NODE". */
12
+ function dragTypeForNode(dragType, node) {
13
+ if (typeof dragType === "function")
14
+ return dragType(node);
15
+ return dragType !== null && dragType !== void 0 ? dragType : "NODE";
16
+ }
9
17
  function useDragHook(node) {
10
18
  const tree = (0, context_1.useTreeApi)();
11
19
  const ids = tree.selectedIds;
12
20
  const [_, ref, preview] = (0, react_dnd_1.useDrag)(() => ({
13
21
  canDrag: () => node.isDraggable,
14
- type: "NODE",
22
+ type: dragTypeForNode(tree.props.dragType, node),
15
23
  item: () => {
16
- // This is fired once at the begging of a drag operation
24
+ // This is fired once at the beginning of a drag operation
17
25
  const dragIds = tree.isSelected(node.id) ? Array.from(ids) : [node.id];
18
26
  tree.dispatch(dnd_slice_1.actions.dragStart(node.id, dragIds));
19
- return { id: node.id, dragIds };
27
+ return { id: node.id, dragIds, data: node.data };
20
28
  },
21
29
  end: () => {
22
30
  tree.hideCursor();
31
+ tree.redrawList();
23
32
  tree.dispatch(dnd_slice_1.actions.dragEnd());
24
33
  },
25
- }), [ids, node]);
34
+ }), [ids, node, tree.props.dragType]);
26
35
  (0, react_1.useEffect)(() => {
27
36
  preview((0, react_dnd_html5_backend_1.getEmptyImage)());
28
37
  }, [preview]);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const drag_hook_1 = require("./drag-hook");
4
+ /* dragTypeForNode only reads node.data when dragType is a function, so a
5
+ minimal stub stands in for a real NodeApi. */
6
+ function nodeWith(data) {
7
+ return { data };
8
+ }
9
+ test("defaults to the internal NODE type when dragType is undefined", () => {
10
+ expect((0, drag_hook_1.dragTypeForNode)(undefined, nodeWith({ id: "a" }))).toBe("NODE");
11
+ });
12
+ test("uses a fixed string dragType for every node", () => {
13
+ expect((0, drag_hook_1.dragTypeForNode)("FILE", nodeWith({ id: "a" }))).toBe("FILE");
14
+ });
15
+ test("resolves a per-node dragType function against the node", () => {
16
+ const dragType = (node) => node.data.kind.toUpperCase();
17
+ expect((0, drag_hook_1.dragTypeForNode)(dragType, nodeWith({ kind: "folder" }))).toBe("FOLDER");
18
+ expect((0, drag_hook_1.dragTypeForNode)(dragType, nodeWith({ kind: "file" }))).toBe("FILE");
19
+ });
@@ -6,8 +6,7 @@ function useValidatedProps(props) {
6
6
  if (props.initialData && props.data) {
7
7
  throw new Error(`React Arborist Tree => Provide either a data or initialData prop, but not both.`);
8
8
  }
9
- if (props.initialData &&
10
- (props.onCreate || props.onDelete || props.onMove || props.onRename)) {
9
+ if (props.initialData && (props.onCreate || props.onDelete || props.onMove || props.onRename)) {
11
10
  throw new Error(`React Arborist Tree => You passed the initialData prop along with a data handler.
12
11
  Use the data prop if you want to provide your own handlers.`);
13
12
  }
@@ -6,7 +6,10 @@ class NodeApi {
6
6
  constructor(params) {
7
7
  this.handleClick = (e) => {
8
8
  if (e.metaKey && !this.tree.props.disableMultiSelection) {
9
- this.isSelected ? this.deselect() : this.selectMulti();
9
+ if (this.isSelected)
10
+ this.deselect();
11
+ else
12
+ this.selectMulti();
10
13
  }
11
14
  else if (e.shiftKey && !this.tree.props.disableMultiSelection) {
12
15
  this.selectContiguous();
@@ -2,7 +2,7 @@ import { EditResult } from "../types/handlers";
2
2
  import { Identity, IdObj } from "../types/utils";
3
3
  import { TreeProps } from "../types/tree-props";
4
4
  import { MutableRefObject } from "react";
5
- import { Align, FixedSizeList, ListOnItemsRenderedProps } from "react-window";
5
+ import { Align, FixedSizeList, ListOnItemsRenderedProps, VariableSizeList } from "react-window";
6
6
  import { DefaultRow } from "../components/default-row";
7
7
  import { DefaultNode } from "../components/default-node";
8
8
  import { NodeApi } from "./node-api";
@@ -14,7 +14,7 @@ import { Store } from "redux";
14
14
  export declare class TreeApi<T> {
15
15
  store: Store<RootState, Actions>;
16
16
  props: TreeProps<T>;
17
- list: MutableRefObject<FixedSizeList | null>;
17
+ list: MutableRefObject<FixedSizeList | VariableSizeList | null>;
18
18
  listEl: MutableRefObject<HTMLDivElement | null>;
19
19
  static editPromise: null | ((args: EditResult) => void);
20
20
  root: NodeApi<T>;
@@ -24,7 +24,8 @@ export declare class TreeApi<T> {
24
24
  idToIndex: {
25
25
  [id: string]: number;
26
26
  };
27
- constructor(store: Store<RootState, Actions>, props: TreeProps<T>, list: MutableRefObject<FixedSizeList | null>, listEl: MutableRefObject<HTMLDivElement | null>);
27
+ private rowOffsets;
28
+ constructor(store: Store<RootState, Actions>, props: TreeProps<T>, list: MutableRefObject<FixedSizeList | VariableSizeList | null>, listEl: MutableRefObject<HTMLDivElement | null>);
28
29
  update(props: TreeProps<T>): void;
29
30
  dispatch(action: Actions): {
30
31
  type: "FOCUS";
@@ -121,7 +122,28 @@ export declare class TreeApi<T> {
121
122
  get width(): string | number;
122
123
  get height(): number;
123
124
  get indent(): number;
125
+ /**
126
+ * The fixed row height. When a `rowHeight` function is supplied for variable
127
+ * heights, this returns the default (24); use `rowHeightAt(index)` to get the
128
+ * height of a specific row.
129
+ */
124
130
  get rowHeight(): number;
131
+ /**
132
+ * The height of the row at `index`, evaluating the `rowHeight` function if
133
+ * given. Falls back to the default height for an out-of-range index so this
134
+ * never feeds an invalid `0` to react-window's `itemSize`.
135
+ */
136
+ rowHeightAt: (index: number) => number;
137
+ /** The pixel offset of the top of the row at `index` from the top of the list. */
138
+ rowTopPosition: (index: number) => number;
139
+ /**
140
+ * Tell the underlying virtualized list to recompute row heights at and after
141
+ * `index`. Call this if a `rowHeight` function's output changes for reasons
142
+ * the tree can't observe (e.g. external state).
143
+ */
144
+ redrawList: (afterIndex?: number) => void;
145
+ /** Lazily-built prefix sum where offsets[i] is the top of row i. */
146
+ private getRowOffsets;
125
147
  get overscanCount(): number;
126
148
  get searchTerm(): string;
127
149
  get matchFn(): (node: NodeApi<T>) => boolean;
@@ -185,8 +207,8 @@ export declare class TreeApi<T> {
185
207
  canDrop(): boolean;
186
208
  hideCursor(): void;
187
209
  showCursor(cursor: Cursor): void;
188
- open(identity: Identity): void;
189
- close(identity: Identity): void;
210
+ open(identity: Identity, redraw?: boolean): void;
211
+ close(identity: Identity, redraw?: boolean): void;
190
212
  toggle(identity: Identity): void;
191
213
  openParents(identity: Identity): void;
192
214
  openSiblings(node: NodeApi<T>): void;