@headless-tree/core 0.0.13 → 0.0.15

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 (125) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/cjs/core/create-tree.js +13 -4
  3. package/lib/cjs/features/async-data-loader/feature.js +73 -48
  4. package/lib/cjs/features/async-data-loader/types.d.ts +17 -14
  5. package/lib/cjs/features/drag-and-drop/feature.js +98 -93
  6. package/lib/cjs/features/drag-and-drop/types.d.ts +17 -29
  7. package/lib/cjs/features/drag-and-drop/types.js +7 -7
  8. package/lib/cjs/features/drag-and-drop/utils.d.ts +18 -3
  9. package/lib/cjs/features/drag-and-drop/utils.js +49 -51
  10. package/lib/cjs/features/expand-all/feature.js +26 -3
  11. package/lib/cjs/features/expand-all/types.d.ts +3 -1
  12. package/lib/cjs/features/hotkeys-core/feature.js +7 -3
  13. package/lib/cjs/features/hotkeys-core/types.d.ts +4 -5
  14. package/lib/cjs/features/keyboard-drag-and-drop/feature.d.ts +2 -0
  15. package/lib/cjs/features/keyboard-drag-and-drop/feature.js +207 -0
  16. package/lib/cjs/features/keyboard-drag-and-drop/types.d.ts +27 -0
  17. package/lib/cjs/features/keyboard-drag-and-drop/types.js +11 -0
  18. package/lib/cjs/features/prop-memoization/feature.js +2 -2
  19. package/lib/cjs/features/prop-memoization/types.d.ts +2 -2
  20. package/lib/cjs/features/renaming/feature.js +1 -1
  21. package/lib/cjs/features/search/feature.js +2 -0
  22. package/lib/cjs/features/search/types.d.ts +2 -2
  23. package/lib/cjs/features/selection/feature.js +4 -4
  24. package/lib/cjs/features/selection/types.d.ts +1 -1
  25. package/lib/cjs/features/sync-data-loader/feature.js +31 -5
  26. package/lib/cjs/features/sync-data-loader/types.d.ts +5 -5
  27. package/lib/cjs/features/tree/feature.js +4 -7
  28. package/lib/cjs/features/tree/types.d.ts +7 -5
  29. package/lib/cjs/index.d.ts +2 -0
  30. package/lib/cjs/index.js +2 -0
  31. package/lib/cjs/mddocs-entry.d.ts +10 -0
  32. package/lib/cjs/test-utils/test-tree-do.d.ts +2 -2
  33. package/lib/cjs/test-utils/test-tree-do.js +19 -6
  34. package/lib/cjs/test-utils/test-tree-expect.d.ts +5 -3
  35. package/lib/cjs/test-utils/test-tree-expect.js +3 -0
  36. package/lib/cjs/test-utils/test-tree.d.ts +2 -1
  37. package/lib/cjs/test-utils/test-tree.js +24 -21
  38. package/lib/cjs/types/core.d.ts +2 -1
  39. package/lib/cjs/utilities/create-on-drop-handler.d.ts +2 -2
  40. package/lib/cjs/utilities/create-on-drop-handler.js +13 -4
  41. package/lib/cjs/utilities/insert-items-at-target.d.ts +2 -2
  42. package/lib/cjs/utilities/insert-items-at-target.js +21 -12
  43. package/lib/cjs/utilities/remove-items-from-parents.d.ts +1 -1
  44. package/lib/cjs/utilities/remove-items-from-parents.js +12 -3
  45. package/lib/esm/core/create-tree.js +13 -4
  46. package/lib/esm/features/async-data-loader/feature.js +73 -48
  47. package/lib/esm/features/async-data-loader/types.d.ts +17 -14
  48. package/lib/esm/features/drag-and-drop/feature.js +99 -94
  49. package/lib/esm/features/drag-and-drop/types.d.ts +17 -29
  50. package/lib/esm/features/drag-and-drop/types.js +6 -6
  51. package/lib/esm/features/drag-and-drop/utils.d.ts +18 -3
  52. package/lib/esm/features/drag-and-drop/utils.js +44 -49
  53. package/lib/esm/features/expand-all/feature.js +26 -3
  54. package/lib/esm/features/expand-all/types.d.ts +3 -1
  55. package/lib/esm/features/hotkeys-core/feature.js +7 -3
  56. package/lib/esm/features/hotkeys-core/types.d.ts +4 -5
  57. package/lib/esm/features/keyboard-drag-and-drop/feature.d.ts +2 -0
  58. package/lib/esm/features/keyboard-drag-and-drop/feature.js +204 -0
  59. package/lib/esm/features/keyboard-drag-and-drop/types.d.ts +27 -0
  60. package/lib/esm/features/keyboard-drag-and-drop/types.js +8 -0
  61. package/lib/esm/features/prop-memoization/feature.js +2 -2
  62. package/lib/esm/features/prop-memoization/types.d.ts +2 -2
  63. package/lib/esm/features/renaming/feature.js +1 -1
  64. package/lib/esm/features/search/feature.js +2 -0
  65. package/lib/esm/features/search/types.d.ts +2 -2
  66. package/lib/esm/features/selection/feature.js +4 -4
  67. package/lib/esm/features/selection/types.d.ts +1 -1
  68. package/lib/esm/features/sync-data-loader/feature.js +31 -5
  69. package/lib/esm/features/sync-data-loader/types.d.ts +5 -5
  70. package/lib/esm/features/tree/feature.js +4 -7
  71. package/lib/esm/features/tree/types.d.ts +7 -5
  72. package/lib/esm/index.d.ts +2 -0
  73. package/lib/esm/index.js +2 -0
  74. package/lib/esm/mddocs-entry.d.ts +10 -0
  75. package/lib/esm/test-utils/test-tree-do.d.ts +2 -2
  76. package/lib/esm/test-utils/test-tree-do.js +19 -6
  77. package/lib/esm/test-utils/test-tree-expect.d.ts +5 -3
  78. package/lib/esm/test-utils/test-tree-expect.js +3 -0
  79. package/lib/esm/test-utils/test-tree.d.ts +2 -1
  80. package/lib/esm/test-utils/test-tree.js +24 -21
  81. package/lib/esm/types/core.d.ts +2 -1
  82. package/lib/esm/utilities/create-on-drop-handler.d.ts +2 -2
  83. package/lib/esm/utilities/create-on-drop-handler.js +13 -4
  84. package/lib/esm/utilities/insert-items-at-target.d.ts +2 -2
  85. package/lib/esm/utilities/insert-items-at-target.js +21 -12
  86. package/lib/esm/utilities/remove-items-from-parents.d.ts +1 -1
  87. package/lib/esm/utilities/remove-items-from-parents.js +12 -3
  88. package/package.json +2 -2
  89. package/src/core/core.spec.ts +31 -0
  90. package/src/core/create-tree.ts +15 -5
  91. package/src/features/async-data-loader/async-data-loader.spec.ts +10 -6
  92. package/src/features/async-data-loader/feature.ts +76 -48
  93. package/src/features/async-data-loader/types.ts +18 -11
  94. package/src/features/drag-and-drop/drag-and-drop.spec.ts +75 -89
  95. package/src/features/drag-and-drop/feature.ts +21 -22
  96. package/src/features/drag-and-drop/types.ts +23 -35
  97. package/src/features/drag-and-drop/utils.ts +67 -57
  98. package/src/features/expand-all/feature.ts +29 -5
  99. package/src/features/expand-all/types.ts +3 -1
  100. package/src/features/hotkeys-core/feature.ts +4 -0
  101. package/src/features/hotkeys-core/types.ts +4 -13
  102. package/src/features/keyboard-drag-and-drop/feature.ts +255 -0
  103. package/src/features/keyboard-drag-and-drop/keyboard-drag-and-drop.spec.ts +401 -0
  104. package/src/features/keyboard-drag-and-drop/types.ts +30 -0
  105. package/src/features/prop-memoization/feature.ts +2 -2
  106. package/src/features/prop-memoization/prop-memoization.spec.ts +2 -2
  107. package/src/features/prop-memoization/types.ts +2 -2
  108. package/src/features/renaming/feature.ts +8 -2
  109. package/src/features/search/feature.ts +2 -0
  110. package/src/features/search/types.ts +2 -2
  111. package/src/features/selection/feature.ts +4 -4
  112. package/src/features/selection/types.ts +1 -1
  113. package/src/features/sync-data-loader/feature.ts +26 -7
  114. package/src/features/sync-data-loader/types.ts +5 -5
  115. package/src/features/tree/feature.ts +8 -11
  116. package/src/features/tree/types.ts +7 -5
  117. package/src/index.ts +2 -0
  118. package/src/mddocs-entry.ts +16 -0
  119. package/src/test-utils/test-tree-do.ts +3 -3
  120. package/src/test-utils/test-tree-expect.ts +7 -2
  121. package/src/test-utils/test-tree.ts +26 -22
  122. package/src/types/core.ts +2 -0
  123. package/src/utilities/create-on-drop-handler.ts +4 -4
  124. package/src/utilities/insert-items-at-target.ts +18 -14
  125. package/src/utilities/remove-items-from-parents.ts +6 -3
@@ -1,6 +1,6 @@
1
1
  import { FeatureImplementation } from "../../types/core";
2
2
  import { DndDataRef, DragLineData } from "./types";
3
- import { canDrop, getDragCode, getDropTarget } from "./utils";
3
+ import { canDrop, getDragCode, getDragTarget } from "./utils";
4
4
  import { makeStateUpdater } from "../../utils";
5
5
 
6
6
  export const dragAndDropFeature: FeatureImplementation = {
@@ -20,17 +20,17 @@ export const dragAndDropFeature: FeatureImplementation = {
20
20
  },
21
21
 
22
22
  treeInstance: {
23
- getDropTarget: ({ tree }) => {
23
+ getDragTarget: ({ tree }) => {
24
24
  return tree.getState().dnd?.dragTarget ?? null;
25
25
  },
26
26
 
27
27
  getDragLineData: ({ tree }): DragLineData | null => {
28
- const target = tree.getDropTarget();
28
+ const target = tree.getDragTarget();
29
29
  const indent = (target?.item.getItemMeta().level ?? 0) + 1;
30
30
 
31
31
  const treeBb = tree.getElement()?.getBoundingClientRect();
32
32
 
33
- if (!target || !treeBb || target.childIndex === null) return null;
33
+ if (!target || !treeBb || !("childIndex" in target)) return null;
34
34
 
35
35
  const leftOffset = target.dragLineLevel * (tree.getConfig().indent ?? 1);
36
36
  const targetItem = tree.getItems()[target.dragLineIndex];
@@ -44,7 +44,7 @@ export const dragAndDropFeature: FeatureImplementation = {
44
44
  if (bb) {
45
45
  return {
46
46
  indent,
47
- top: bb.bottom - treeBb.bottom,
47
+ top: bb.bottom - treeBb.top,
48
48
  left: bb.left + leftOffset - treeBb.left,
49
49
  width: bb.width - leftOffset,
50
50
  };
@@ -77,8 +77,8 @@ export const dragAndDropFeature: FeatureImplementation = {
77
77
  : { display: "none" };
78
78
  },
79
79
 
80
- getContainerProps: ({ prev }) => {
81
- const prevProps = prev?.();
80
+ getContainerProps: ({ prev }, treeLabel) => {
81
+ const prevProps = prev?.(treeLabel);
82
82
  return {
83
83
  ...prevProps,
84
84
  style: {
@@ -93,7 +93,7 @@ export const dragAndDropFeature: FeatureImplementation = {
93
93
  getProps: ({ tree, item, prev }) => ({
94
94
  ...prev?.(),
95
95
 
96
- draggable: tree.getConfig().isItemDraggable?.(item) ?? true,
96
+ draggable: true,
97
97
 
98
98
  onDragStart: (e: DragEvent) => {
99
99
  const selectedItems = tree.getSelectedItems();
@@ -131,7 +131,7 @@ export const dragAndDropFeature: FeatureImplementation = {
131
131
  }
132
132
  dataRef.current.lastDragCode = nextDragCode;
133
133
 
134
- const target = getDropTarget(e, item, tree);
134
+ const target = getDragTarget(e, item, tree);
135
135
 
136
136
  if (
137
137
  !tree.getState().dnd?.draggedItems &&
@@ -179,9 +179,9 @@ export const dragAndDropFeature: FeatureImplementation = {
179
179
  tree.getConfig().onCompleteForeignDrop?.(draggedItems);
180
180
  },
181
181
 
182
- onDrop: (e: DragEvent) => {
182
+ onDrop: async (e: DragEvent) => {
183
183
  const dataRef = tree.getDataRef<DndDataRef>();
184
- const target = getDropTarget(e, item, tree);
184
+ const target = getDragTarget(e, item, tree);
185
185
 
186
186
  if (!canDrop(e.dataTransfer, target, tree)) {
187
187
  return;
@@ -195,37 +195,36 @@ export const dragAndDropFeature: FeatureImplementation = {
195
195
  tree.applySubStateUpdate("dnd", null);
196
196
 
197
197
  if (draggedItems) {
198
- config.onDrop?.(draggedItems, target);
198
+ await config.onDrop?.(draggedItems, target);
199
199
  } else if (e.dataTransfer) {
200
- config.onDropForeignDragObject?.(e.dataTransfer, target);
200
+ await config.onDropForeignDragObject?.(e.dataTransfer, target);
201
201
  }
202
- // TODO rebuild tree?
203
202
  },
204
203
  }),
205
204
 
206
- isDropTarget: ({ tree, item }) => {
207
- const target = tree.getDropTarget();
205
+ isDragTarget: ({ tree, item }) => {
206
+ const target = tree.getDragTarget();
208
207
  return target ? target.item.getId() === item.getId() : false;
209
208
  },
210
209
 
211
- isDropTargetAbove: ({ tree, item }) => {
212
- const target = tree.getDropTarget();
210
+ isDragTargetAbove: ({ tree, item }) => {
211
+ const target = tree.getDragTarget();
213
212
 
214
213
  if (
215
214
  !target ||
216
- target.childIndex === null ||
215
+ !("childIndex" in target) ||
217
216
  target.item !== item.getParent()
218
217
  )
219
218
  return false;
220
219
  return target.childIndex === item.getItemMeta().posInSet;
221
220
  },
222
221
 
223
- isDropTargetBelow: ({ tree, item }) => {
224
- const target = tree.getDropTarget();
222
+ isDragTargetBelow: ({ tree, item }) => {
223
+ const target = tree.getDragTarget();
225
224
 
226
225
  if (
227
226
  !target ||
228
- target.childIndex === null ||
227
+ !("childIndex" in target) ||
229
228
  target.item !== item.getParent()
230
229
  )
231
230
  return false;
@@ -1,24 +1,24 @@
1
1
  import { ItemInstance, SetStateFn } from "../../types/core";
2
2
 
3
- export type DndDataRef = {
3
+ export interface DndDataRef {
4
4
  lastDragCode?: string;
5
5
  lastAllowDrop?: boolean;
6
- };
6
+ }
7
7
 
8
- export type DndState<T> = {
8
+ export interface DndState<T> {
9
9
  draggedItems?: ItemInstance<T>[];
10
10
  draggingOverItem?: ItemInstance<T>;
11
- dragTarget?: DropTarget<T>;
12
- };
11
+ dragTarget?: DragTarget<T>;
12
+ }
13
13
 
14
- export type DragLineData = {
14
+ export interface DragLineData {
15
15
  indent: number;
16
16
  top: number;
17
17
  left: number;
18
18
  width: number;
19
- };
19
+ }
20
20
 
21
- export type DropTarget<T> =
21
+ export type DragTarget<T> =
22
22
  | {
23
23
  item: ItemInstance<T>;
24
24
  childIndex: number;
@@ -27,14 +27,10 @@ export type DropTarget<T> =
27
27
  dragLineLevel: number;
28
28
  }
29
29
  | {
30
- item: ItemInstance<T>; // TODO just omit values instead of nulls; or maybe just make it union of dropTarget+itemInstance?
31
- childIndex: null;
32
- insertionIndex: null;
33
- dragLineIndex: null;
34
- dragLineLevel: null;
30
+ item: ItemInstance<T>;
35
31
  };
36
32
 
37
- export enum DropTargetPosition {
33
+ export enum DragTargetPosition {
38
34
  Top = "top",
39
35
  Bottom = "bottom",
40
36
  Item = "item",
@@ -53,10 +49,8 @@ export type DragAndDropFeatureDef<T> = {
53
49
  reorderAreaPercentage?: number;
54
50
  canReorder?: boolean;
55
51
 
56
- // TODO better document difference to canDrag(), or unify both
57
- isItemDraggable?: (item: ItemInstance<T>) => boolean;
58
52
  canDrag?: (items: ItemInstance<T>[]) => boolean;
59
- canDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => boolean;
53
+ canDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => boolean;
60
54
 
61
55
  indent?: number;
62
56
 
@@ -66,37 +60,31 @@ export type DragAndDropFeatureDef<T> = {
66
60
  };
67
61
  canDropForeignDragObject?: (
68
62
  dataTransfer: DataTransfer,
69
- target: DropTarget<T>,
63
+ target: DragTarget<T>,
70
64
  ) => boolean;
71
- onDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => void;
65
+ onDrop?: (
66
+ items: ItemInstance<T>[],
67
+ target: DragTarget<T>,
68
+ ) => void | Promise<void>;
72
69
  onDropForeignDragObject?: (
73
70
  dataTransfer: DataTransfer,
74
- target: DropTarget<T>,
75
- ) => void;
76
-
77
- /** Runs in the onDragEnd event, if `ev.dataTransfer.dropEffect` is not `none`, i.e. the drop
78
- * was not aborted. No target is provided as parameter since the target may be a foreign drop target.
79
- * This is useful to seperate out the logic to move dragged items out of their previous parents.
80
- * Use `onDrop` to handle drop-related logic.
81
- *
82
- * This ignores the `canDrop` handler, since the drop target is unknown in this handler.
83
- */
84
- // onSuccessfulDragEnd?: (items: ItemInstance<T>[]) => void;
85
-
71
+ target: DragTarget<T>,
72
+ ) => void | Promise<void>;
86
73
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
87
74
  };
88
75
  treeInstance: {
89
- getDropTarget: () => DropTarget<T> | null;
76
+ getDragTarget: () => DragTarget<T> | null;
90
77
  getDragLineData: () => DragLineData | null;
78
+
91
79
  getDragLineStyle: (
92
80
  topOffset?: number,
93
81
  leftOffset?: number,
94
82
  ) => Record<string, any>;
95
83
  };
96
84
  itemInstance: {
97
- isDropTarget: () => boolean;
98
- isDropTargetAbove: () => boolean; // TODO still correct?
99
- isDropTargetBelow: () => boolean; // TODO still correct?
85
+ isDragTarget: () => boolean;
86
+ isDragTargetAbove: () => boolean;
87
+ isDragTargetBelow: () => boolean;
100
88
  isDraggingOver: () => boolean;
101
89
  };
102
90
  hotkeys: never;
@@ -1,7 +1,7 @@
1
1
  import { ItemInstance, TreeInstance } from "../../types/core";
2
- import { DropTarget } from "./types";
2
+ import { DragTarget } from "./types";
3
3
 
4
- enum ItemDropCategory {
4
+ export enum ItemDropCategory {
5
5
  Item,
6
6
  ExpandedFolder,
7
7
  LastInGroup,
@@ -28,7 +28,7 @@ type TargetPlacement =
28
28
 
29
29
  export const canDrop = (
30
30
  dataTransfer: DataTransfer | null,
31
- target: DropTarget<any>,
31
+ target: DragTarget<any>,
32
32
  tree: TreeInstance<any>,
33
33
  ) => {
34
34
  const draggedItems = tree.getState().dnd?.draggedItems;
@@ -52,7 +52,8 @@ export const canDrop = (
52
52
  if (
53
53
  !draggedItems &&
54
54
  dataTransfer &&
55
- !config.canDropForeignDragObject?.(dataTransfer, target)
55
+ config.canDropForeignDragObject &&
56
+ !config.canDropForeignDragObject(dataTransfer, target)
56
57
  ) {
57
58
  return false;
58
59
  }
@@ -60,19 +61,37 @@ export const canDrop = (
60
61
  return true;
61
62
  };
62
63
 
63
- const getItemDropCategory = (item: ItemInstance<any>) => {
64
+ export const getItemDropCategory = (item: ItemInstance<any>) => {
64
65
  if (item.isExpanded()) {
65
66
  return ItemDropCategory.ExpandedFolder;
66
67
  }
67
68
 
68
69
  const parent = item.getParent();
69
- if (parent && item.getIndexInParent() === parent.getItemMeta().setSize - 1) {
70
+ if (parent && item.getIndexInParent() === item.getItemMeta().setSize - 1) {
70
71
  return ItemDropCategory.LastInGroup;
71
72
  }
72
73
 
73
74
  return ItemDropCategory.Item;
74
75
  };
75
76
 
77
+ export const getInsertionIndex = <T>(
78
+ children: ItemInstance<T>[],
79
+ childIndex: number,
80
+ draggedItems: ItemInstance<T>[] | undefined,
81
+ ) => {
82
+ const numberOfDragItemsBeforeTarget =
83
+ children
84
+ .slice(0, childIndex)
85
+ .reduce(
86
+ (counter, child) =>
87
+ child && draggedItems?.some((i) => i.getId() === child.getId())
88
+ ? ++counter
89
+ : counter,
90
+ 0,
91
+ ) ?? 0;
92
+ return childIndex - numberOfDragItemsBeforeTarget;
93
+ };
94
+
76
95
  const getTargetPlacement = (
77
96
  e: any,
78
97
  item: ItemInstance<any>,
@@ -88,8 +107,8 @@ const getTargetPlacement = (
88
107
  }
89
108
 
90
109
  const bb = item.getElement()?.getBoundingClientRect();
91
- const topPercent = bb ? (e.pageY - bb.top) / bb.height : 0.5;
92
- const leftPixels = bb ? e.pageX - bb.left : 0;
110
+ const topPercent = bb ? (e.clientY - bb.top) / bb.height : 0.5;
111
+ const leftPixels = bb ? e.clientX - bb.left : 0;
93
112
  const targetDropCategory = getItemDropCategory(item);
94
113
  const reorderAreaPercentage = !canMakeChild
95
114
  ? 0.5
@@ -111,9 +130,10 @@ const getTargetPlacement = (
111
130
  if (topPercent < 0.5) {
112
131
  return { type: PlacementType.ReorderAbove };
113
132
  }
133
+ const minLevel = item.getItemBelow()?.getItemMeta().level ?? 0;
114
134
  return {
115
135
  type: PlacementType.Reparent,
116
- reparentLevel: Math.floor(leftPixels / indent),
136
+ reparentLevel: Math.max(minLevel, Math.floor(leftPixels / indent)),
117
137
  };
118
138
  }
119
139
  // if not at left of item area, treat as if it was a normal item
@@ -152,31 +172,41 @@ const getNthParent = (
152
172
  return getNthParent(item.getParent()!, n);
153
173
  };
154
174
 
155
- export const getDropTarget = (
175
+ /** @param item refers to the bottom-most item of the container, at which bottom is being reparented on (e.g. root-1-2-6) */
176
+ export const getReparentTarget = <T>(
177
+ item: ItemInstance<T>,
178
+ reparentLevel: number,
179
+ draggedItems: ItemInstance<T>[] | undefined,
180
+ ) => {
181
+ const itemMeta = item.getItemMeta();
182
+ const reparentedTarget = getNthParent(item, reparentLevel - 1);
183
+ const targetItemAbove = getNthParent(item, reparentLevel); // .getItemBelow()!;
184
+ const targetIndex = targetItemAbove.getIndexInParent() + 1;
185
+
186
+ return {
187
+ item: reparentedTarget,
188
+ childIndex: targetIndex,
189
+ insertionIndex: getInsertionIndex(
190
+ reparentedTarget.getChildren(),
191
+ targetIndex,
192
+ draggedItems,
193
+ ),
194
+ dragLineIndex: itemMeta.index + 1,
195
+ dragLineLevel: reparentLevel,
196
+ };
197
+ };
198
+
199
+ export const getDragTarget = (
156
200
  e: any,
157
201
  item: ItemInstance<any>,
158
202
  tree: TreeInstance<any>,
159
203
  canReorder = tree.getConfig().canReorder,
160
- ): DropTarget<any> => {
161
- const draggedItems = tree.getState().dnd?.draggedItems ?? [];
204
+ ): DragTarget<any> => {
205
+ const draggedItems = tree.getState().dnd?.draggedItems;
162
206
  const itemMeta = item.getItemMeta();
163
207
  const parent = item.getParent();
164
- const itemTarget: DropTarget<any> = {
165
- item,
166
- childIndex: null,
167
- insertionIndex: null,
168
- dragLineIndex: null,
169
- dragLineLevel: null,
170
- };
171
- const parentTarget: DropTarget<any> | null = parent
172
- ? {
173
- item: parent,
174
- childIndex: null,
175
- insertionIndex: null,
176
- dragLineIndex: null,
177
- dragLineLevel: null,
178
- }
179
- : null;
208
+ const itemTarget: DragTarget<any> = { item };
209
+ const parentTarget: DragTarget<any> | null = parent ? { item: parent } : null;
180
210
  const canBecomeSibling =
181
211
  parentTarget && canDrop(e.dataTransfer, parentTarget, tree);
182
212
 
@@ -193,8 +223,8 @@ export const getDropTarget = (
193
223
  }
194
224
 
195
225
  if (!canReorder && parent && !canBecomeSibling) {
196
- // TODO! this breaks in story DND/Can Drop. Maybe move this logic into a composable DropTargetStrategy[] ?
197
- return getDropTarget(e, parent, tree, false);
226
+ // TODO! this breaks in story DND/Can Drop. Maybe move this logic into a composable DragTargetStrategy[] ?
227
+ return getDragTarget(e, parent, tree, false);
198
228
  }
199
229
 
200
230
  if (!parent) {
@@ -207,47 +237,27 @@ export const getDropTarget = (
207
237
  }
208
238
 
209
239
  if (!canBecomeSibling) {
210
- return getDropTarget(e, parent, tree, false);
240
+ return getDragTarget(e, parent, tree, false);
211
241
  }
212
242
 
213
243
  if (placement.type === PlacementType.Reparent) {
214
- const reparentedTarget = getNthParent(item, placement.reparentLevel - 1);
215
- const targetItemAbove = getNthParent(item, placement.reparentLevel); // .getItemBelow()!;
216
- const targetIndex = targetItemAbove.getIndexInParent() + 1;
217
-
218
- // TODO possibly count items dragged out above the new target
219
-
220
- return {
221
- item: reparentedTarget,
222
- childIndex: targetIndex,
223
- insertionIndex: targetIndex,
224
- dragLineIndex: itemMeta.index + 1,
225
- dragLineLevel: placement.reparentLevel,
226
- };
244
+ return getReparentTarget(item, placement.reparentLevel, draggedItems);
227
245
  }
228
246
 
229
247
  const maybeAddOneForBelow =
230
248
  placement.type === PlacementType.ReorderAbove ? 0 : 1;
231
249
  const childIndex = item.getIndexInParent() + maybeAddOneForBelow;
232
250
 
233
- const numberOfDragItemsBeforeTarget =
234
- parent
235
- .getChildren()
236
- .slice(0, childIndex)
237
- .reduce(
238
- (counter, child) =>
239
- child && draggedItems?.some((i) => i.getId() === child.getId())
240
- ? ++counter
241
- : counter,
242
- 0,
243
- ) ?? 0;
244
-
245
251
  return {
246
252
  item: parent,
247
253
  dragLineIndex: itemMeta.index + maybeAddOneForBelow,
248
254
  dragLineLevel: itemMeta.level,
249
255
  childIndex,
250
256
  // TODO performance could be improved by computing this only when dragcode changed
251
- insertionIndex: childIndex - numberOfDragItemsBeforeTarget,
257
+ insertionIndex: getInsertionIndex(
258
+ parent.getChildren(),
259
+ childIndex,
260
+ draggedItems,
261
+ ),
252
262
  };
253
263
  };
@@ -1,5 +1,4 @@
1
1
  import { FeatureImplementation } from "../../types/core";
2
- import { poll } from "../../utils";
3
2
 
4
3
  export const expandAllFeature: FeatureImplementation = {
5
4
  key: "expand-all",
@@ -27,22 +26,47 @@ export const expandAllFeature: FeatureImplementation = {
27
26
  }
28
27
 
29
28
  item.expand();
30
- await poll(() => !tree.getState().loadingItems.includes(item.getId()));
29
+ await tree.waitForItemChildrenLoaded(item.getId());
31
30
  await Promise.all(
32
31
  item.getChildren().map(async (child) => {
33
- await poll(
34
- () => !tree.getState().loadingItems.includes(child.getId()),
35
- );
32
+ await tree.waitForItemChildrenLoaded(item.getId());
36
33
  await child?.expandAll(cancelToken);
37
34
  }),
38
35
  );
39
36
  },
40
37
 
41
38
  collapseAll: ({ item }) => {
39
+ if (!item.isExpanded()) return;
42
40
  for (const child of item.getChildren()) {
43
41
  child?.collapseAll();
44
42
  }
45
43
  item.collapse();
46
44
  },
47
45
  },
46
+
47
+ hotkeys: {
48
+ expandSelected: {
49
+ hotkey: "Control+Shift+Plus",
50
+ handler: async (_, tree) => {
51
+ const cancelToken = { current: false };
52
+ const cancelHandler = (e: KeyboardEvent) => {
53
+ if (e.key === "Escape") {
54
+ cancelToken.current = true;
55
+ }
56
+ };
57
+ document.addEventListener("keydown", cancelHandler);
58
+ await Promise.all(
59
+ tree.getSelectedItems().map((item) => item.expandAll(cancelToken)),
60
+ );
61
+ document.removeEventListener("keydown", cancelHandler);
62
+ },
63
+ },
64
+
65
+ collapseSelected: {
66
+ hotkey: "Control+Shift+-",
67
+ handler: (_, tree) => {
68
+ tree.getSelectedItems().forEach((item) => item.collapseAll());
69
+ },
70
+ },
71
+ },
48
72
  };
@@ -1,3 +1,5 @@
1
+ export interface ExpandAllDataRef {}
2
+
1
3
  export type ExpandAllFeatureDef = {
2
4
  state: {};
3
5
  config: {};
@@ -9,5 +11,5 @@ export type ExpandAllFeatureDef = {
9
11
  expandAll: (cancelToken?: { current: boolean }) => Promise<void>;
10
12
  collapseAll: () => void;
11
13
  };
12
- hotkeys: never;
14
+ hotkeys: "expandSelected" | "collapseSelected";
13
15
  };
@@ -8,6 +8,8 @@ import { HotkeyConfig, HotkeysCoreDataRef } from "./types";
8
8
  const specialKeys: Record<string, RegExp> = {
9
9
  Letter: /^[a-z]$/,
10
10
  LetterOrNumber: /^[a-z0-9]$/,
11
+ Plus: /^\+$/,
12
+ Space: /^ $/,
11
13
  };
12
14
 
13
15
  const testHotkeyMatch = (
@@ -46,6 +48,7 @@ export const hotkeysCoreFeature: FeatureImplementation = {
46
48
  data.current.pressedKeys ??= new Set();
47
49
  const newMatch = !data.current.pressedKeys.has(e.key);
48
50
  data.current.pressedKeys.add(e.key);
51
+ console.log("HOTKEYS", data.current.pressedKeys);
49
52
 
50
53
  const hotkeyName = findHotkeyMatch(
51
54
  data.current.pressedKeys,
@@ -71,6 +74,7 @@ export const hotkeysCoreFeature: FeatureImplementation = {
71
74
  if (hotkeyConfig.preventDefault) e.preventDefault();
72
75
 
73
76
  hotkeyConfig.handler(e, tree as any);
77
+ tree.getConfig().onTreeHotkey?.(hotkeyName, e);
74
78
  };
75
79
 
76
80
  const keyup = (e: KeyboardEvent) => {
@@ -1,8 +1,4 @@
1
- import {
2
- CustomHotkeysConfig,
3
- ItemInstance,
4
- TreeInstance,
5
- } from "../../types/core";
1
+ import { CustomHotkeysConfig, TreeInstance } from "../../types/core";
6
2
 
7
3
  export interface HotkeyConfig<T> {
8
4
  hotkey: string;
@@ -13,22 +9,17 @@ export interface HotkeyConfig<T> {
13
9
  handler: (e: KeyboardEvent, tree: TreeInstance<T>) => void;
14
10
  }
15
11
 
16
- export type HotkeysCoreDataRef = {
12
+ export interface HotkeysCoreDataRef {
17
13
  keydownHandler?: (e: KeyboardEvent) => void;
18
14
  keyupHandler?: (e: KeyboardEvent) => void;
19
15
  pressedKeys: Set<string>;
20
- };
16
+ }
21
17
 
22
18
  export type HotkeysCoreFeatureDef<T> = {
23
19
  state: {};
24
20
  config: {
25
21
  hotkeys?: CustomHotkeysConfig<T>;
26
- onTreeHotkey?: (name: string, element: HTMLElement) => void;
27
- onItemHotkey?: (
28
- name: string,
29
- item: ItemInstance<T>,
30
- element: HTMLElement,
31
- ) => void;
22
+ onTreeHotkey?: (name: string, e: KeyboardEvent) => void;
32
23
  };
33
24
  treeInstance: {};
34
25
  itemInstance: {};