react-arborist 3.1.0 → 3.3.0-rc.1

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,7 +5,7 @@ export declare type DndState = {
5
5
  cursor: Cursor;
6
6
  dragIds: string[];
7
7
  parentId: null | string;
8
- index: number;
8
+ index: number | null;
9
9
  };
10
10
  export declare const actions: {
11
11
  cursor(cursor: Cursor): {
@@ -20,10 +20,10 @@ export declare const actions: {
20
20
  dragEnd(): {
21
21
  type: "DND_DRAG_END";
22
22
  };
23
- hovering(parentId: string | null, index: number): {
23
+ hovering(parentId: string | null, index: number | null): {
24
24
  type: "DND_HOVERING";
25
25
  parentId: string | null;
26
- index: number;
26
+ index: number | null;
27
27
  };
28
28
  };
29
29
  export declare function reducer(state: DndState | undefined, action: ActionTypes<typeof actions>): DndState;
@@ -2,6 +2,8 @@ import { ActionTypes } from "../types/utils";
2
2
  import { actions as dnd } from "./dnd-slice";
3
3
  export declare type DragSlice = {
4
4
  id: string | null;
5
- idWillReceiveDrop: string | null;
5
+ selectedIds: string[];
6
+ destinationParentId: string | null;
7
+ destinationIndex: number | null;
6
8
  };
7
9
  export declare function reducer(state: DragSlice | undefined, action: ActionTypes<typeof dnd>): DragSlice;
@@ -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";
package/dist/utils.d.ts CHANGED
@@ -5,9 +5,9 @@ export declare function bound(n: number, min: number, max: number): number;
5
5
  export declare function isItem(node: NodeApi<any> | null): boolean | null;
6
6
  export declare function isClosed(node: NodeApi<any> | null): boolean | null;
7
7
  /**
8
- * Is first param a decendent of the second param
8
+ * Is first param a descendant of the second param
9
9
  */
10
- export declare const isDecendent: (a: NodeApi<any>, b: NodeApi<any>) => boolean;
10
+ export declare const isDescendant: (a: NodeApi<any>, b: NodeApi<any>) => boolean;
11
11
  export declare const indexOf: (node: NodeApi<any>) => number;
12
12
  export declare function noop(): void;
13
13
  export declare function dfs(node: NodeApi<any>, id: string): NodeApi<any> | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-arborist",
3
- "version": "3.1.0",
3
+ "version": "3.3.0-rc.1",
4
4
  "license": "MIT",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -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";
@@ -26,7 +26,7 @@ function getNodesAroundCursor(
26
26
  hover: HoverData
27
27
  ): [NodeApi | null, NodeApi | null] {
28
28
  if (!node) {
29
- // We're hoving over the empty part of the list, not over an item,
29
+ // We're hovering over the empty part of the list, not over an item,
30
30
  // Put the cursor below the last item which is "prev"
31
31
  return [prev, null];
32
32
  }
@@ -83,7 +83,10 @@ export type ComputedDrop = {
83
83
  cursor: Cursor | null;
84
84
  };
85
85
 
86
- function dropAt(parentId: string | undefined, index: number): DropResult {
86
+ function dropAt(
87
+ parentId: string | undefined,
88
+ index: number | null
89
+ ): DropResult {
87
90
  return { parentId: parentId || null, index };
88
91
  }
89
92
 
@@ -135,7 +138,7 @@ export function computeDrop(args: Args): ComputedDrop {
135
138
  /* Hovering over the middle of a folder */
136
139
  if (node && node.isInternal && hover.inMiddle) {
137
140
  return {
138
- drop: dropAt(node.id, 0),
141
+ drop: dropAt(node.id, null),
139
142
  cursor: highlightCursor(node.id),
140
143
  };
141
144
  }
@@ -31,7 +31,7 @@ export function useDragHook<T>(node: NodeApi<T>): ConnectDragSource {
31
31
  safeRun(tree.props.onMove, {
32
32
  dragIds,
33
33
  parentId: parentId === ROOT_ID ? null : parentId,
34
- index,
34
+ index: index === null ? 0 : index, // When it's null it was dropped over a folder
35
35
  dragNodes: tree.dragNodes,
36
36
  parentNode: tree.get(parentId),
37
37
  });
@@ -8,7 +8,7 @@ import { actions as dnd } from "../state/dnd-slice";
8
8
 
9
9
  export type DropResult = {
10
10
  parentId: string | null;
11
- index: number;
11
+ index: number | null;
12
12
  };
13
13
 
14
14
  export function useDropHook(
@@ -130,6 +130,16 @@ export class NodeApi<T = any> {
130
130
  return this.parent?.children![i + 1] ?? null;
131
131
  }
132
132
 
133
+ isAncestorOf(node: NodeApi<T> | null) {
134
+ if (!node) return false;
135
+ let ancestor: NodeApi<T> | null = node;
136
+ while (ancestor) {
137
+ if (ancestor.id === this.id) return true;
138
+ ancestor = ancestor.parent;
139
+ }
140
+ return false;
141
+ }
142
+
133
143
  select() {
134
144
  this.tree.select(this);
135
145
  }
@@ -370,21 +370,33 @@ export class TreeApi<T> {
370
370
  }
371
371
 
372
372
  deselectAll() {
373
- this.dispatch(selection.clear());
374
- this.dispatch(selection.anchor(null));
375
- this.dispatch(selection.mostRecent(null));
373
+ this.setSelection({ ids: [], anchor: null, mostRecent: null });
376
374
  safeRun(this.props.onSelect, this.selectedNodes);
377
375
  }
378
376
 
379
377
  selectAll() {
380
- 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
+ });
381
383
  this.dispatch(focus(this.lastNode?.id));
382
- this.dispatch(selection.anchor(this.firstNode));
383
- this.dispatch(selection.mostRecent(this.lastNode));
384
384
  if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
385
385
  safeRun(this.props.onSelect, this.selectedNodes);
386
386
  }
387
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
+
388
400
  /* Drag and Drop */
389
401
 
390
402
  get cursorParentId() {
@@ -407,6 +419,18 @@ export class TreeApi<T> {
407
419
  .filter((n) => !!n) as NodeApi<T>[];
408
420
  }
409
421
 
422
+ get dragNode() {
423
+ return this.get(this.state.nodes.drag.id);
424
+ }
425
+
426
+ get dragDestinationParent() {
427
+ return this.get(this.state.nodes.drag.destinationParentId);
428
+ }
429
+
430
+ get dragDestinationIndex() {
431
+ return this.state.nodes.drag.destinationIndex;
432
+ }
433
+
410
434
  canDrop() {
411
435
  if (this.isFiltered) return false;
412
436
  const parentNode = this.get(this.state.dnd.parentId) ?? this.root;
@@ -416,7 +440,7 @@ export class TreeApi<T> {
416
440
  for (const drag of dragNodes) {
417
441
  if (!drag) return false;
418
442
  if (!parentNode) return false;
419
- if (drag.isInternal && utils.isDecendent(parentNode, drag)) return false;
443
+ if (drag.isInternal && utils.isDescendant(parentNode, drag)) return false;
420
444
  }
421
445
 
422
446
  // Allow the user to insert their own logic
@@ -424,7 +448,7 @@ export class TreeApi<T> {
424
448
  return !isDisabled({
425
449
  parentNode,
426
450
  dragNodes: this.dragNodes,
427
- index: this.state.dnd.index,
451
+ index: this.state.dnd.index || 0,
428
452
  });
429
453
  } else if (typeof isDisabled == "string") {
430
454
  // @ts-ignore
@@ -594,7 +618,8 @@ export class TreeApi<T> {
594
618
  willReceiveDrop(node: string | IdObj | null) {
595
619
  const id = identifyNull(node);
596
620
  if (!id) return false;
597
- return id === this.state.nodes.drag.idWillReceiveDrop;
621
+ const { destinationParentId, destinationIndex } = this.state.nodes.drag;
622
+ return id === destinationParentId && destinationIndex === null;
598
623
  }
599
624
 
600
625
  /* Tree Event Handlers */
@@ -8,7 +8,7 @@ export type DndState = {
8
8
  cursor: Cursor;
9
9
  dragIds: string[];
10
10
  parentId: null | string;
11
- index: number;
11
+ index: number | null;
12
12
  };
13
13
 
14
14
  /* Actions */
@@ -22,7 +22,7 @@ export const actions = {
22
22
  dragEnd() {
23
23
  return { type: "DND_DRAG_END" as const };
24
24
  },
25
- hovering(parentId: string | null, index: number) {
25
+ hovering(parentId: string | null, index: number | null) {
26
26
  return { type: "DND_HOVERING" as const, parentId, index };
27
27
  },
28
28
  };
@@ -1,27 +1,43 @@
1
1
  import { ActionTypes } from "../types/utils";
2
2
  import { actions as dnd } from "./dnd-slice";
3
+ import { initialState } from "./initial";
3
4
 
4
5
  /* Types */
5
6
 
6
- export type DragSlice = { id: string | null; idWillReceiveDrop: string | null };
7
+ export type DragSlice = {
8
+ id: string | null;
9
+ selectedIds: string[];
10
+ destinationParentId: string | null;
11
+ destinationIndex: number | null;
12
+ };
7
13
 
8
14
  /* Reducer */
9
15
 
10
16
  export function reducer(
11
- state: DragSlice = { id: null, idWillReceiveDrop: null },
17
+ state: DragSlice = initialState().nodes.drag,
12
18
  action: ActionTypes<typeof dnd>
13
- ) {
19
+ ): DragSlice {
14
20
  switch (action.type) {
15
21
  case "DND_DRAG_START":
16
- return { ...state, id: action.id };
22
+ return { ...state, id: action.id, selectedIds: action.dragIds };
17
23
  case "DND_DRAG_END":
18
- return { ...state, id: null };
19
- case "DND_CURSOR":
20
- const c = action.cursor;
21
- if (c.type === "highlight" && c.id !== state.idWillReceiveDrop) {
22
- return { ...state, idWillReceiveDrop: c.id };
23
- } else if (c.type !== "highlight" && state.idWillReceiveDrop !== null) {
24
- return { ...state, idWillReceiveDrop: null };
24
+ return {
25
+ ...state,
26
+ id: null,
27
+ destinationParentId: null,
28
+ destinationIndex: null,
29
+ selectedIds: [],
30
+ };
31
+ case "DND_HOVERING":
32
+ if (
33
+ action.parentId !== state.destinationParentId ||
34
+ action.index != state.destinationIndex
35
+ ) {
36
+ return {
37
+ ...state,
38
+ destinationParentId: action.parentId,
39
+ destinationIndex: action.index,
40
+ };
25
41
  } else {
26
42
  return state;
27
43
  }
@@ -7,7 +7,12 @@ export const initialState = (props?: TreeProps<any>): RootState => ({
7
7
  open: { filtered: {}, unfiltered: props?.initialOpenState ?? {} },
8
8
  focus: { id: null, treeFocused: false },
9
9
  edit: { id: null },
10
- drag: { id: null, idWillReceiveDrop: null },
10
+ drag: {
11
+ id: null,
12
+ selectedIds: [],
13
+ destinationParentId: null,
14
+ destinationIndex: null,
15
+ },
11
16
  selection: { ids: new Set(), anchor: null, mostRecent: null },
12
17
  },
13
18
  dnd: {
@@ -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,7 +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
+ import { useDragDropManager } from "react-dnd";
9
9
 
10
10
  export interface TreeProps<T> {
11
11
  /* Data Options */
@@ -76,5 +76,5 @@ export interface TreeProps<T> {
76
76
  dndRootElement?: globalThis.Node | null;
77
77
  onClick?: MouseEventHandler;
78
78
  onContextMenu?: MouseEventHandler;
79
- dndManager?: ReturnType<typeof useDragDropManager>
79
+ dndManager?: ReturnType<typeof useDragDropManager>;
80
80
  }
package/src/utils.ts CHANGED
@@ -15,9 +15,9 @@ export function isClosed(node: NodeApi<any> | null) {
15
15
  }
16
16
 
17
17
  /**
18
- * Is first param a decendent of the second param
18
+ * Is first param a descendant of the second param
19
19
  */
20
- export const isDecendent = (a: NodeApi<any>, b: NodeApi<any>) => {
20
+ export const isDescendant = (a: NodeApi<any>, b: NodeApi<any>) => {
21
21
  let n: NodeApi<any> | null = a;
22
22
  while (n) {
23
23
  if (n.id === b.id) return true;