react-arborist 3.0.1 → 3.1.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.
@@ -5,6 +5,7 @@ import { ElementType, MouseEventHandler } from "react";
5
5
  import { ListOnScrollProps } from "react-window";
6
6
  import { NodeApi } from "../interfaces/node-api";
7
7
  import { OpenMap } from "../state/open-slice";
8
+ import { useDragDropManager } from "react-dnd";
8
9
  export interface TreeProps<T> {
9
10
  data?: readonly T[];
10
11
  initialData?: readonly T[];
@@ -51,4 +52,5 @@ export interface TreeProps<T> {
51
52
  dndRootElement?: globalThis.Node | null;
52
53
  onClick?: MouseEventHandler;
53
54
  onContextMenu?: MouseEventHandler;
55
+ dndManager?: ReturnType<typeof useDragDropManager>;
54
56
  }
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ };
package/package.json CHANGED
@@ -1,11 +1,23 @@
1
1
  {
2
2
  "name": "react-arborist",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "license": "MIT",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/module.js",
8
8
  "types": "dist/index.d.ts",
9
+ "sideEffects": false,
10
+ "scripts": {
11
+ "start": "run-p 'start:**'",
12
+ "build": "npm-run-all clean -p 'build:**'",
13
+ "test": "jest",
14
+ "build:js": "parcel build --target main --target module",
15
+ "build:types": "tsc --outDir dist",
16
+ "clean": "rimraf dist",
17
+ "start:js": "parcel watch --target main --target module --no-hmr --no-cache",
18
+ "start:types": "tsc --outDir dist --watch",
19
+ "prepack": "yarn build"
20
+ },
9
21
  "repository": {
10
22
  "type": "git",
11
23
  "url": "https://github.com/brimdata/react-arborist.git"
@@ -30,15 +42,6 @@
30
42
  "redux": "^4.1.1",
31
43
  "use-sync-external-store": "^1.2.0"
32
44
  },
33
- "scripts": {
34
- "build": "run-p 'build:**'",
35
- "build:js": "parcel build --target main --target module",
36
- "build:types": "tsc --outDir dist",
37
- "watch:js": "parcel watch --target main --target module --no-hmr --no-cache",
38
- "watch:types": "tsc --outDir dist --watch",
39
- "watch": "run-p 'watch:**'",
40
- "prepack": "yarn build"
41
- },
42
45
  "peerDependencies": {
43
46
  "react": ">= 16.14",
44
47
  "react-dom": ">= 16.14"
@@ -49,9 +52,11 @@
49
52
  "@types/react": "^18.0.0",
50
53
  "@types/react-window": "^1.8.5",
51
54
  "@types/use-sync-external-store": "^0.0.3",
52
- "jest": "^27.5.1",
55
+ "jest": "^29.4.1",
53
56
  "npm-run-all": "^4.1.5",
54
57
  "parcel": "^2.3.2",
58
+ "rimraf": "^4.1.2",
59
+ "ts-jest": "^29.0.5",
55
60
  "typescript": "^4.6.2"
56
61
  }
57
62
  }
@@ -65,7 +65,7 @@ export function TreeProvider<T>({
65
65
  /* Change selection based on props */
66
66
  useEffect(() => {
67
67
  if (api.props.selection) {
68
- api.select(api.props.selection);
68
+ api.select(api.props.selection, { focus: false });
69
69
  } else {
70
70
  api.deselectAll();
71
71
  }
@@ -86,6 +86,7 @@ export function TreeProvider<T>({
86
86
  <DndProvider
87
87
  backend={HTML5Backend}
88
88
  options={{ rootElement: api.props.dndRootElement || undefined }}
89
+ {...(treeProps.dndManager && { manager: treeProps.dndManager })}
89
90
  >
90
91
  {children}
91
92
  </DndProvider>
@@ -29,24 +29,35 @@ function flattenAndFilterTree<T>(
29
29
  root: NodeApi<T>,
30
30
  isMatch: (n: NodeApi<T>) => boolean
31
31
  ): NodeApi<T>[] {
32
- function collect(node: NodeApi<T>) {
33
- let result: NodeApi<T>[] = [];
34
- const yes = !node.isRoot && isMatch(node);
32
+ const matches: Record<string, boolean> = {};
33
+ const list: NodeApi<T>[] = [];
35
34
 
36
- if (node.children) {
37
- for (let child of node.children) {
38
- result = result.concat(collect(child));
35
+ function markMatch(node: NodeApi<T>) {
36
+ const yes = !node.isRoot && isMatch(node);
37
+ if (yes) {
38
+ matches[node.id] = true;
39
+ let parent = node.parent;
40
+ while (parent) {
41
+ matches[parent.id] = true;
42
+ parent = parent.parent;
39
43
  }
40
44
  }
41
- if (result.length) {
42
- if (!node.isRoot) result.unshift(node);
43
- return result;
45
+ if (node.children) {
46
+ for (let child of node.children) markMatch(child);
44
47
  }
45
- if (yes) return [node];
46
- else return [];
47
48
  }
48
49
 
49
- const list = collect(root).filter((n) => n.parent?.isOpen);
50
+ function collect(node: NodeApi<T>) {
51
+ if (node.level >= 0 && matches[node.id]) {
52
+ list.push(node);
53
+ }
54
+ if (node.isOpen) {
55
+ node.children?.forEach(collect);
56
+ }
57
+ }
58
+
59
+ markMatch(root);
60
+ collect(root);
50
61
  list.forEach(assignRowIndex);
51
62
  return list;
52
63
  }
@@ -0,0 +1,15 @@
1
+ import { createStore } from "redux";
2
+ import { rootReducer } from "../state/root-reducer";
3
+ import { TreeProps } from "../types/tree-props";
4
+ import { TreeApi } from "./tree-api";
5
+
6
+ function setupApi(props: TreeProps<any>) {
7
+ const store = createStore(rootReducer);
8
+ return new TreeApi(store, props, { current: null }, { current: null });
9
+ }
10
+
11
+ test("tree.canDrop()", () => {
12
+ expect(setupApi({ disableDrop: true }).canDrop()).toBe(false);
13
+ expect(setupApi({ disableDrop: () => false }).canDrop()).toBe(true);
14
+ expect(setupApi({ disableDrop: false }).canDrop()).toBe(true);
15
+ });
@@ -323,15 +323,18 @@ export class TreeApi<T> {
323
323
  this.focus(this.at(index));
324
324
  }
325
325
 
326
- select(node: Identity, opts: { align?: Align } = {}) {
326
+ select(node: Identity, opts: { align?: Align; focus?: boolean } = {}) {
327
327
  if (!node) return;
328
+ const changeFocus = opts.focus !== false;
328
329
  const id = identify(node);
329
- this.dispatch(focus(id));
330
+ if (changeFocus) this.dispatch(focus(id));
330
331
  this.dispatch(selection.only(id));
331
332
  this.dispatch(selection.anchor(id));
332
333
  this.dispatch(selection.mostRecent(id));
333
334
  this.scrollTo(id, opts.align);
334
- if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
335
+ if (this.focusedNode && changeFocus) {
336
+ safeRun(this.props.onFocus, this.focusedNode);
337
+ }
335
338
  safeRun(this.props.onSelect, this.selectedNodes);
336
339
  }
337
340
 
@@ -408,7 +411,7 @@ export class TreeApi<T> {
408
411
  if (this.isFiltered) return false;
409
412
  const parentNode = this.get(this.state.dnd.parentId) ?? this.root;
410
413
  const dragNodes = this.dragNodes;
411
- const check = this.props.disableDrop;
414
+ const isDisabled = this.props.disableDrop;
412
415
 
413
416
  for (const drag of dragNodes) {
414
417
  if (!drag) return false;
@@ -417,17 +420,17 @@ export class TreeApi<T> {
417
420
  }
418
421
 
419
422
  // Allow the user to insert their own logic
420
- if (typeof check == "function") {
421
- return check({
423
+ if (typeof isDisabled == "function") {
424
+ return !isDisabled({
422
425
  parentNode,
423
426
  dragNodes: this.dragNodes,
424
427
  index: this.state.dnd.index,
425
428
  });
426
- } else if (typeof check == "string") {
429
+ } else if (typeof isDisabled == "string") {
427
430
  // @ts-ignore
428
- return !!parentNode.data[check];
429
- } else if (typeof check === "boolean") {
430
- return check;
431
+ return !parentNode.data[isDisabled];
432
+ } else if (typeof isDisabled === "boolean") {
433
+ return !isDisabled;
431
434
  } else {
432
435
  return true;
433
436
  }
@@ -5,6 +5,7 @@ import { ElementType, MouseEventHandler } from "react";
5
5
  import { ListOnScrollProps } from "react-window";
6
6
  import { NodeApi } from "../interfaces/node-api";
7
7
  import { OpenMap } from "../state/open-slice";
8
+ import { useDragDropManager } from "react-dnd"
8
9
 
9
10
  export interface TreeProps<T> {
10
11
  /* Data Options */
@@ -75,4 +76,5 @@ export interface TreeProps<T> {
75
76
  dndRootElement?: globalThis.Node | null;
76
77
  onClick?: MouseEventHandler;
77
78
  onContextMenu?: MouseEventHandler;
79
+ dndManager?: ReturnType<typeof useDragDropManager>
78
80
  }