react-arborist 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,9 +20,15 @@ export declare const actions: {
20
20
  type: "SELECTION_REMOVE";
21
21
  ids: string[];
22
22
  };
23
- set: (ids: Set<string>) => {
24
- type: "SELECTION_SET";
23
+ set: (args: {
24
+ ids: Set<string>;
25
+ anchor: string | null;
26
+ mostRecent: string | null;
27
+ }) => {
25
28
  ids: Set<string>;
29
+ anchor: string | null;
30
+ mostRecent: string | null;
31
+ type: "SELECTION_SET";
26
32
  };
27
33
  mostRecent: (id: string | null | IdObj) => {
28
34
  type: "SELECTION_MOST_RECENT";
@@ -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/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "react-arborist",
3
- "version": "3.0.2",
3
+ "version": "3.2.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,
9
10
  "scripts": {
10
- "start": "run-p 'watch:**'",
11
+ "start": "run-p 'start:**'",
11
12
  "build": "npm-run-all clean -p 'build:**'",
12
13
  "test": "jest",
13
14
  "build:js": "parcel build --target main --target module",
@@ -14,7 +14,6 @@ import {
14
14
  TreeApiContext,
15
15
  } from "../context";
16
16
  import { TreeApi } from "../interfaces/tree-api";
17
- import { IdObj } from "../types/utils";
18
17
  import { initialState } from "../state/initial";
19
18
  import { rootReducer, RootState } from "../state/root-reducer";
20
19
  import { HTML5Backend } from "react-dnd-html5-backend";
@@ -65,7 +64,7 @@ export function TreeProvider<T>({
65
64
  /* Change selection based on props */
66
65
  useEffect(() => {
67
66
  if (api.props.selection) {
68
- api.select(api.props.selection);
67
+ api.select(api.props.selection, { focus: false });
69
68
  } else {
70
69
  api.deselectAll();
71
70
  }
@@ -86,6 +85,7 @@ export function TreeProvider<T>({
86
85
  <DndProvider
87
86
  backend={HTML5Backend}
88
87
  options={{ rootElement: api.props.dndRootElement || undefined }}
88
+ {...(treeProps.dndManager && { manager: treeProps.dndManager })}
89
89
  >
90
90
  {children}
91
91
  </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
  }
@@ -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
 
@@ -367,21 +370,33 @@ export class TreeApi<T> {
367
370
  }
368
371
 
369
372
  deselectAll() {
370
- this.dispatch(selection.clear());
371
- this.dispatch(selection.anchor(null));
372
- this.dispatch(selection.mostRecent(null));
373
+ this.setSelection({ ids: [], anchor: null, mostRecent: null });
373
374
  safeRun(this.props.onSelect, this.selectedNodes);
374
375
  }
375
376
 
376
377
  selectAll() {
377
- this.dispatch(selection.set(new Set(Object.keys(this.idToIndex))));
378
+ this.setSelection({
379
+ ids: Object.keys(this.idToIndex),
380
+ anchor: this.firstNode,
381
+ mostRecent: this.lastNode,
382
+ });
378
383
  this.dispatch(focus(this.lastNode?.id));
379
- this.dispatch(selection.anchor(this.firstNode));
380
- this.dispatch(selection.mostRecent(this.lastNode));
381
384
  if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
382
385
  safeRun(this.props.onSelect, this.selectedNodes);
383
386
  }
384
387
 
388
+ setSelection(args: {
389
+ ids: (IdObj | string)[] | null;
390
+ anchor: IdObj | string | null;
391
+ mostRecent: IdObj | string | null;
392
+ }) {
393
+ const ids = new Set(args.ids?.map(identify));
394
+ const anchor = identifyNull(args.anchor);
395
+ const mostRecent = identifyNull(args.mostRecent);
396
+ this.dispatch(selection.set({ ids, anchor, mostRecent }));
397
+ safeRun(this.props.onSelect, this.selectedNodes);
398
+ }
399
+
385
400
  /* Drag and Drop */
386
401
 
387
402
  get cursorParentId() {
@@ -28,9 +28,13 @@ export const actions = {
28
28
  ids: (Array.isArray(id) ? id : [id]).map(identify),
29
29
  }),
30
30
 
31
- set: (ids: Set<string>) => ({
31
+ set: (args: {
32
+ ids: Set<string>;
33
+ anchor: string | null;
34
+ mostRecent: string | null;
35
+ }) => ({
32
36
  type: "SELECTION_SET" as const,
33
- ids,
37
+ ...args,
34
38
  }),
35
39
 
36
40
  mostRecent: (id: string | null | IdObj) => ({
@@ -64,7 +68,12 @@ export function reducer(
64
68
  action.ids.forEach((id) => ids.delete(id));
65
69
  return { ...state, ids: new Set(ids) };
66
70
  case "SELECTION_SET":
67
- return { ...state, ids: new Set(action.ids) };
71
+ return {
72
+ ...state,
73
+ ids: action.ids,
74
+ mostRecent: action.mostRecent,
75
+ anchor: action.anchor,
76
+ };
68
77
  case "SELECTION_MOST_RECENT":
69
78
  return { ...state, mostRecent: action.id };
70
79
  case "SELECTION_ANCHOR":
@@ -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
  }