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.
- package/README.md +14 -0
- package/dist/main/components/cursor.js +1 -2
- package/dist/main/components/default-container.js +31 -2
- package/dist/main/components/default-cursor.js +1 -1
- package/dist/main/components/default-drag-preview.d.ts +1 -1
- package/dist/main/components/default-drag-preview.js +1 -1
- package/dist/main/components/default-row.d.ts +1 -1
- package/dist/main/components/default-row.js +1 -1
- package/dist/main/components/list-outer-element.js +1 -1
- package/dist/main/components/provider.d.ts +1 -1
- package/dist/main/components/provider.js +2 -2
- package/dist/main/components/provider.test.js +70 -0
- package/dist/main/components/row-container.d.ts +1 -1
- package/dist/main/components/row-container.js +2 -3
- package/dist/main/dnd/drag-hook.d.ts +2 -0
- package/dist/main/dnd/drag-hook.js +13 -4
- package/dist/main/dnd/drag-hook.test.d.ts +1 -0
- package/dist/main/dnd/drag-hook.test.js +19 -0
- package/dist/main/hooks/use-validated-props.js +1 -2
- package/dist/main/interfaces/node-api.js +4 -1
- package/dist/main/interfaces/tree-api.d.ts +27 -5
- package/dist/main/interfaces/tree-api.js +98 -14
- package/dist/main/interfaces/tree-api.test.js +31 -0
- package/dist/main/state/drag-slice.js +1 -2
- package/dist/main/types/dnd.d.ts +3 -1
- package/dist/main/types/state.d.ts +1 -1
- package/dist/main/types/tree-props.d.ts +4 -1
- package/dist/module/components/cursor.js +1 -2
- package/dist/module/components/default-container.js +32 -3
- package/dist/module/components/default-cursor.js +1 -1
- package/dist/module/components/default-drag-preview.d.ts +1 -1
- package/dist/module/components/default-drag-preview.js +1 -1
- package/dist/module/components/default-row.d.ts +1 -1
- package/dist/module/components/default-row.js +1 -1
- package/dist/module/components/list-outer-element.js +1 -1
- package/dist/module/components/provider.d.ts +1 -1
- package/dist/module/components/provider.js +4 -4
- package/dist/module/components/provider.test.js +71 -1
- package/dist/module/components/row-container.d.ts +1 -1
- package/dist/module/components/row-container.js +2 -3
- package/dist/module/dnd/compute-drop.js +1 -1
- package/dist/module/dnd/drag-hook.d.ts +2 -0
- package/dist/module/dnd/drag-hook.js +12 -4
- package/dist/module/dnd/drag-hook.test.d.ts +1 -0
- package/dist/module/dnd/drag-hook.test.js +17 -0
- package/dist/module/hooks/use-validated-props.js +1 -2
- package/dist/module/interfaces/node-api.js +4 -1
- package/dist/module/interfaces/tree-api.d.ts +27 -5
- package/dist/module/interfaces/tree-api.js +98 -14
- package/dist/module/interfaces/tree-api.test.js +31 -0
- package/dist/module/state/drag-slice.js +1 -2
- package/dist/module/types/dnd.d.ts +3 -1
- package/dist/module/types/state.d.ts +1 -1
- package/dist/module/types/tree-props.d.ts +4 -1
- package/package.json +27 -27
- package/src/components/cursor.tsx +1 -2
- package/src/components/default-container.tsx +40 -19
- package/src/components/default-cursor.tsx +1 -5
- package/src/components/default-drag-preview.tsx +3 -16
- package/src/components/default-node.tsx +0 -1
- package/src/components/default-row.tsx +2 -13
- package/src/components/drag-preview-container.tsx +1 -1
- package/src/components/list-inner-element.tsx +1 -1
- package/src/components/list-outer-element.tsx +2 -3
- package/src/components/provider.test.tsx +85 -9
- package/src/components/provider.tsx +8 -23
- package/src/components/row-container.tsx +4 -9
- package/src/components/tree.tsx +2 -6
- package/src/context.ts +2 -3
- package/src/data/create-index.ts +0 -1
- package/src/data/create-list.ts +1 -2
- package/src/data/create-root.ts +2 -9
- package/src/data/simple-tree.ts +5 -3
- package/src/dnd/compute-drop.ts +6 -15
- package/src/dnd/drag-hook.test.ts +22 -0
- package/src/dnd/drag-hook.ts +15 -6
- package/src/dnd/measure-hover.ts +2 -6
- package/src/dnd/outer-drop-hook.ts +1 -1
- package/src/hooks/use-fresh-node.ts +0 -1
- package/src/hooks/use-simple-tree.ts +2 -8
- package/src/hooks/use-validated-props.ts +4 -8
- package/src/interfaces/node-api.ts +2 -2
- package/src/interfaces/tree-api.test.ts +35 -0
- package/src/interfaces/tree-api.ts +103 -36
- package/src/state/dnd-slice.ts +1 -1
- package/src/state/drag-slice.ts +2 -5
- package/src/state/edit-slice.ts +1 -4
- package/src/state/focus-slice.ts +1 -1
- package/src/state/open-slice.ts +2 -5
- package/src/state/selection-slice.ts +2 -6
- package/src/types/dnd.ts +6 -1
- package/src/types/handlers.ts +1 -3
- package/src/types/renderers.ts +0 -1
- package/src/types/state.ts +1 -1
- package/src/types/tree-props.ts +15 -10
- package/src/types/utils.ts +2 -3
- 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.
|
|
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)(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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:
|
|
22
|
+
type: dragTypeForNode(tree.props.dragType, node),
|
|
15
23
|
item: () => {
|
|
16
|
-
// This is fired once at the
|
|
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
|
-
|
|
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
|
-
|
|
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;
|