@headless-tree/core 0.0.10 → 0.0.12

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 (148) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/cjs/core/build-proxified-instance.d.ts +2 -0
  3. package/lib/cjs/core/build-proxified-instance.js +58 -0
  4. package/lib/cjs/core/build-static-instance.d.ts +2 -0
  5. package/lib/cjs/core/build-static-instance.js +26 -0
  6. package/lib/cjs/core/create-tree.js +62 -40
  7. package/lib/cjs/features/async-data-loader/feature.d.ts +1 -4
  8. package/lib/cjs/features/async-data-loader/feature.js +35 -23
  9. package/lib/cjs/features/async-data-loader/types.d.ts +4 -6
  10. package/lib/cjs/features/drag-and-drop/feature.d.ts +2 -3
  11. package/lib/cjs/features/drag-and-drop/feature.js +79 -44
  12. package/lib/cjs/features/drag-and-drop/types.d.ts +15 -6
  13. package/lib/cjs/features/drag-and-drop/utils.d.ts +2 -3
  14. package/lib/cjs/features/drag-and-drop/utils.js +140 -37
  15. package/lib/cjs/features/expand-all/feature.d.ts +1 -5
  16. package/lib/cjs/features/expand-all/feature.js +12 -6
  17. package/lib/cjs/features/hotkeys-core/feature.d.ts +1 -3
  18. package/lib/cjs/features/main/types.d.ts +8 -2
  19. package/lib/cjs/features/prop-memoization/feature.d.ts +2 -0
  20. package/lib/cjs/features/prop-memoization/feature.js +48 -0
  21. package/lib/cjs/features/prop-memoization/types.d.ts +10 -0
  22. package/lib/cjs/features/prop-memoization/types.js +2 -0
  23. package/lib/cjs/features/renaming/feature.d.ts +1 -4
  24. package/lib/cjs/features/renaming/feature.js +36 -22
  25. package/lib/cjs/features/renaming/types.d.ts +2 -2
  26. package/lib/cjs/features/search/feature.d.ts +1 -4
  27. package/lib/cjs/features/search/feature.js +38 -24
  28. package/lib/cjs/features/search/types.d.ts +0 -1
  29. package/lib/cjs/features/selection/feature.d.ts +1 -4
  30. package/lib/cjs/features/selection/feature.js +54 -35
  31. package/lib/cjs/features/selection/types.d.ts +1 -1
  32. package/lib/cjs/features/sync-data-loader/feature.d.ts +1 -3
  33. package/lib/cjs/features/sync-data-loader/feature.js +7 -2
  34. package/lib/cjs/features/tree/feature.d.ts +1 -5
  35. package/lib/cjs/features/tree/feature.js +97 -92
  36. package/lib/cjs/features/tree/types.d.ts +5 -8
  37. package/lib/cjs/index.d.ts +5 -1
  38. package/lib/cjs/index.js +4 -1
  39. package/lib/cjs/mddocs-entry.d.ts +10 -0
  40. package/lib/cjs/test-utils/test-tree-do.d.ts +23 -0
  41. package/lib/cjs/test-utils/test-tree-do.js +99 -0
  42. package/lib/cjs/test-utils/test-tree-expect.d.ts +15 -0
  43. package/lib/cjs/test-utils/test-tree-expect.js +62 -0
  44. package/lib/cjs/test-utils/test-tree.d.ts +47 -0
  45. package/lib/cjs/test-utils/test-tree.js +203 -0
  46. package/lib/cjs/types/core.d.ts +39 -24
  47. package/lib/cjs/utilities/errors.d.ts +1 -0
  48. package/lib/cjs/utilities/errors.js +5 -0
  49. package/lib/cjs/utilities/insert-items-at-target.js +10 -3
  50. package/lib/cjs/utilities/remove-items-from-parents.js +14 -8
  51. package/lib/cjs/utils.d.ts +3 -3
  52. package/lib/cjs/utils.js +6 -6
  53. package/lib/esm/core/build-proxified-instance.d.ts +2 -0
  54. package/lib/esm/core/build-proxified-instance.js +54 -0
  55. package/lib/esm/core/build-static-instance.d.ts +2 -0
  56. package/lib/esm/core/build-static-instance.js +22 -0
  57. package/lib/esm/core/create-tree.js +62 -40
  58. package/lib/esm/features/async-data-loader/feature.d.ts +1 -4
  59. package/lib/esm/features/async-data-loader/feature.js +35 -23
  60. package/lib/esm/features/async-data-loader/types.d.ts +4 -6
  61. package/lib/esm/features/drag-and-drop/feature.d.ts +2 -3
  62. package/lib/esm/features/drag-and-drop/feature.js +79 -44
  63. package/lib/esm/features/drag-and-drop/types.d.ts +15 -6
  64. package/lib/esm/features/drag-and-drop/utils.d.ts +2 -3
  65. package/lib/esm/features/drag-and-drop/utils.js +138 -34
  66. package/lib/esm/features/expand-all/feature.d.ts +1 -5
  67. package/lib/esm/features/expand-all/feature.js +12 -6
  68. package/lib/esm/features/hotkeys-core/feature.d.ts +1 -3
  69. package/lib/esm/features/main/types.d.ts +8 -2
  70. package/lib/esm/features/prop-memoization/feature.d.ts +2 -0
  71. package/lib/esm/features/prop-memoization/feature.js +45 -0
  72. package/lib/esm/features/prop-memoization/types.d.ts +10 -0
  73. package/lib/esm/features/prop-memoization/types.js +1 -0
  74. package/lib/esm/features/renaming/feature.d.ts +1 -4
  75. package/lib/esm/features/renaming/feature.js +36 -22
  76. package/lib/esm/features/renaming/types.d.ts +2 -2
  77. package/lib/esm/features/search/feature.d.ts +1 -4
  78. package/lib/esm/features/search/feature.js +38 -24
  79. package/lib/esm/features/search/types.d.ts +0 -1
  80. package/lib/esm/features/selection/feature.d.ts +1 -4
  81. package/lib/esm/features/selection/feature.js +54 -35
  82. package/lib/esm/features/selection/types.d.ts +1 -1
  83. package/lib/esm/features/sync-data-loader/feature.d.ts +1 -3
  84. package/lib/esm/features/sync-data-loader/feature.js +7 -2
  85. package/lib/esm/features/tree/feature.d.ts +1 -5
  86. package/lib/esm/features/tree/feature.js +98 -93
  87. package/lib/esm/features/tree/types.d.ts +5 -8
  88. package/lib/esm/index.d.ts +5 -1
  89. package/lib/esm/index.js +4 -1
  90. package/lib/esm/mddocs-entry.d.ts +10 -0
  91. package/lib/esm/test-utils/test-tree-do.d.ts +23 -0
  92. package/lib/esm/test-utils/test-tree-do.js +95 -0
  93. package/lib/esm/test-utils/test-tree-expect.d.ts +15 -0
  94. package/lib/esm/test-utils/test-tree-expect.js +58 -0
  95. package/lib/esm/test-utils/test-tree.d.ts +47 -0
  96. package/lib/esm/test-utils/test-tree.js +199 -0
  97. package/lib/esm/types/core.d.ts +39 -24
  98. package/lib/esm/utilities/errors.d.ts +1 -0
  99. package/lib/esm/utilities/errors.js +1 -0
  100. package/lib/esm/utilities/insert-items-at-target.js +10 -3
  101. package/lib/esm/utilities/remove-items-from-parents.js +14 -8
  102. package/lib/esm/utils.d.ts +3 -3
  103. package/lib/esm/utils.js +3 -3
  104. package/package.json +7 -3
  105. package/src/core/build-proxified-instance.ts +117 -0
  106. package/src/core/build-static-instance.ts +27 -0
  107. package/src/core/core.spec.ts +210 -0
  108. package/src/core/create-tree.ts +73 -78
  109. package/src/features/async-data-loader/async-data-loader.spec.ts +124 -0
  110. package/src/features/async-data-loader/feature.ts +34 -44
  111. package/src/features/async-data-loader/types.ts +4 -6
  112. package/src/features/drag-and-drop/drag-and-drop.spec.ts +717 -0
  113. package/src/features/drag-and-drop/feature.ts +88 -63
  114. package/src/features/drag-and-drop/types.ts +24 -10
  115. package/src/features/drag-and-drop/utils.ts +197 -56
  116. package/src/features/expand-all/expand-all.spec.ts +56 -0
  117. package/src/features/expand-all/feature.ts +9 -24
  118. package/src/features/hotkeys-core/feature.ts +5 -14
  119. package/src/features/main/types.ts +14 -1
  120. package/src/features/prop-memoization/feature.ts +51 -0
  121. package/src/features/prop-memoization/prop-memoization.spec.ts +68 -0
  122. package/src/features/prop-memoization/types.ts +11 -0
  123. package/src/features/renaming/feature.ts +37 -45
  124. package/src/features/renaming/renaming.spec.ts +127 -0
  125. package/src/features/renaming/types.ts +2 -2
  126. package/src/features/search/feature.ts +36 -46
  127. package/src/features/search/search.spec.ts +117 -0
  128. package/src/features/search/types.ts +0 -1
  129. package/src/features/selection/feature.ts +50 -53
  130. package/src/features/selection/selection.spec.ts +219 -0
  131. package/src/features/selection/types.ts +0 -2
  132. package/src/features/sync-data-loader/feature.ts +9 -18
  133. package/src/features/tree/feature.ts +101 -144
  134. package/src/features/tree/tree.spec.ts +475 -0
  135. package/src/features/tree/types.ts +5 -9
  136. package/src/index.ts +6 -1
  137. package/src/mddocs-entry.ts +13 -0
  138. package/src/test-utils/test-tree-do.ts +136 -0
  139. package/src/test-utils/test-tree-expect.ts +86 -0
  140. package/src/test-utils/test-tree.ts +227 -0
  141. package/src/types/core.ts +76 -108
  142. package/src/utilities/errors.ts +2 -0
  143. package/src/utilities/insert-items-at-target.ts +10 -3
  144. package/src/utilities/remove-items-from-parents.ts +15 -10
  145. package/src/utils.spec.ts +89 -0
  146. package/src/utils.ts +6 -6
  147. package/tsconfig.json +1 -0
  148. package/vitest.config.ts +6 -0
@@ -1,20 +1,17 @@
1
- import { FeatureDefs, FeatureImplementation } from "../../types/core";
2
- import { DndDataRef, DragAndDropFeatureDef, DragLineData } from "./types";
1
+ import { FeatureImplementation } from "../../types/core";
2
+ import { DndDataRef, DragLineData } from "./types";
3
3
  import { canDrop, getDragCode, getDropTarget } from "./utils";
4
4
  import { makeStateUpdater } from "../../utils";
5
5
 
6
- export const dragAndDropFeature: FeatureImplementation<
7
- any,
8
- DragAndDropFeatureDef<any>,
9
- FeatureDefs<any>
10
- > = {
11
- key: "dragAndDrop",
6
+ export const dragAndDropFeature: FeatureImplementation = {
7
+ key: "drag-and-drop",
12
8
  deps: ["selection"],
13
9
 
14
10
  getDefaultConfig: (defaultConfig, tree) => ({
15
11
  canDrop: (_, target) => target.item.isFolder(),
16
12
  canDropForeignDragObject: () => false,
17
13
  setDndState: makeStateUpdater("dnd", tree),
14
+ canReorder: true,
18
15
  ...defaultConfig,
19
16
  }),
20
17
 
@@ -22,62 +19,83 @@ export const dragAndDropFeature: FeatureImplementation<
22
19
  dnd: "setDndState",
23
20
  },
24
21
 
25
- createTreeInstance: (prev, tree) => ({
26
- ...prev,
27
-
28
- getDropTarget: () => {
22
+ treeInstance: {
23
+ getDropTarget: ({ tree }) => {
29
24
  return tree.getState().dnd?.dragTarget ?? null;
30
25
  },
31
26
 
32
- getDragLineData: (): DragLineData | null => {
27
+ getDragLineData: ({ tree }): DragLineData | null => {
33
28
  const target = tree.getDropTarget();
34
- const intend = (target?.item.getItemMeta().level ?? 0) + 1;
29
+ const indent = (target?.item.getItemMeta().level ?? 0) + 1;
30
+
31
+ const treeBb = tree.getElement()?.getBoundingClientRect();
35
32
 
36
- if (!target || target.childIndex === null) return null;
33
+ if (!target || !treeBb || target.childIndex === null) return null;
37
34
 
38
- const children = target.item.getChildren();
35
+ const leftOffset = target.dragLineLevel * (tree.getConfig().indent ?? 1);
36
+ const targetItem = tree.getItems()[target.dragLineIndex];
39
37
 
40
- if (target.childIndex === children.length) {
41
- const bb = children[target.childIndex - 1]
42
- ?.getElement()
38
+ if (!targetItem) {
39
+ const bb = tree
40
+ .getItems()
41
+ [target.dragLineIndex - 1]?.getElement()
43
42
  ?.getBoundingClientRect();
44
43
 
45
44
  if (bb) {
46
45
  return {
47
- intend,
48
- top: bb.bottom,
49
- left: bb.left,
50
- right: bb.right,
46
+ indent,
47
+ top: bb.bottom - treeBb.bottom,
48
+ left: bb.left + leftOffset - treeBb.left,
49
+ width: bb.width - leftOffset,
51
50
  };
52
51
  }
53
52
  }
54
53
 
55
- const bb = children[target.childIndex]
56
- ?.getElement()
57
- ?.getBoundingClientRect();
54
+ const bb = targetItem.getElement()?.getBoundingClientRect();
58
55
 
59
56
  if (bb) {
60
57
  return {
61
- intend,
62
- top: bb.top,
63
- left: bb.left,
64
- right: bb.right,
58
+ indent,
59
+ top: bb.top - treeBb.top,
60
+ left: bb.left + leftOffset - treeBb.left,
61
+ width: bb.width - leftOffset,
65
62
  };
66
63
  }
67
64
 
68
65
  return null;
69
66
  },
70
- }),
71
67
 
72
- createItemInstance: (prev, item, tree) => ({
73
- ...prev,
68
+ getDragLineStyle: ({ tree }, topOffset = -1, leftOffset = -8) => {
69
+ const dragLine = tree.getDragLineData();
70
+ return dragLine
71
+ ? {
72
+ top: `${dragLine.top + topOffset}px`,
73
+ left: `${dragLine.left + leftOffset}px`,
74
+ width: `${dragLine.width - leftOffset}px`,
75
+ pointerEvents: "none", // important to prevent capturing drag events
76
+ }
77
+ : { display: "none" };
78
+ },
79
+
80
+ getContainerProps: ({ prev }) => {
81
+ const prevProps = prev?.();
82
+ return {
83
+ ...prevProps,
84
+ style: {
85
+ ...prevProps?.style,
86
+ position: "relative",
87
+ },
88
+ };
89
+ },
90
+ },
74
91
 
75
- getProps: () => ({
76
- ...prev.getProps(),
92
+ itemInstance: {
93
+ getProps: ({ tree, item, prev }) => ({
94
+ ...prev?.(),
77
95
 
78
96
  draggable: tree.getConfig().isItemDraggable?.(item) ?? true,
79
97
 
80
- onDragStart: item.getMemoizedProp("dnd/onDragStart", () => (e) => {
98
+ onDragStart: (e: DragEvent) => {
81
99
  const selectedItems = tree.getSelectedItems();
82
100
  const items = selectedItems.includes(item) ? selectedItems : [item];
83
101
  const config = tree.getConfig();
@@ -100,40 +118,47 @@ export const dragAndDropFeature: FeatureImplementation<
100
118
  draggedItems: items,
101
119
  draggingOverItem: tree.getFocusedItem(),
102
120
  });
103
- }),
121
+ },
104
122
 
105
- onDragOver: item.getMemoizedProp("dnd/onDragOver", () => (e) => {
106
- const target = getDropTarget(e, item, tree);
123
+ onDragOver: (e: DragEvent) => {
107
124
  const dataRef = tree.getDataRef<DndDataRef>();
125
+ const nextDragCode = getDragCode(e, item, tree);
126
+ if (nextDragCode === dataRef.current.lastDragCode) {
127
+ if (dataRef.current.lastAllowDrop) {
128
+ e.preventDefault();
129
+ }
130
+ return;
131
+ }
132
+ dataRef.current.lastDragCode = nextDragCode;
133
+
134
+ const target = getDropTarget(e, item, tree);
108
135
 
109
136
  if (
110
137
  !tree.getState().dnd?.draggedItems &&
111
- !tree.getConfig().canDropForeignDragObject?.(e.dataTransfer, target)
138
+ (!e.dataTransfer ||
139
+ !tree
140
+ .getConfig()
141
+ .canDropForeignDragObject?.(e.dataTransfer, target))
112
142
  ) {
143
+ dataRef.current.lastAllowDrop = false;
113
144
  return;
114
145
  }
115
146
 
116
147
  if (!canDrop(e.dataTransfer, target, tree)) {
148
+ dataRef.current.lastAllowDrop = false;
117
149
  return;
118
150
  }
119
151
 
120
- e.preventDefault();
121
- const nextDragCode = getDragCode(target);
122
-
123
- if (nextDragCode === dataRef.current.lastDragCode) {
124
- return;
125
- }
126
-
127
- dataRef.current.lastDragCode = nextDragCode;
128
-
129
152
  tree.applySubStateUpdate("dnd", (state) => ({
130
153
  ...state,
131
154
  dragTarget: target,
132
155
  draggingOverItem: item,
133
156
  }));
134
- }),
157
+ dataRef.current.lastAllowDrop = true;
158
+ e.preventDefault();
159
+ },
135
160
 
136
- onDragLeave: item.getMemoizedProp("dnd/onDragLeave", () => () => {
161
+ onDragLeave: () => {
137
162
  const dataRef = tree.getDataRef<DndDataRef>();
138
163
  dataRef.current.lastDragCode = "no-drag";
139
164
  tree.applySubStateUpdate("dnd", (state) => ({
@@ -141,20 +166,20 @@ export const dragAndDropFeature: FeatureImplementation<
141
166
  draggingOverItem: undefined,
142
167
  dragTarget: undefined,
143
168
  }));
144
- }),
169
+ },
145
170
 
146
- onDragEnd: item.getMemoizedProp("dnd/onDragEnd", () => (e) => {
171
+ onDragEnd: (e: DragEvent) => {
147
172
  const draggedItems = tree.getState().dnd?.draggedItems;
148
173
  tree.applySubStateUpdate("dnd", null);
149
174
 
150
- if (e.dataTransfer.dropEffect === "none" || !draggedItems) {
175
+ if (e.dataTransfer?.dropEffect === "none" || !draggedItems) {
151
176
  return;
152
177
  }
153
178
 
154
179
  tree.getConfig().onCompleteForeignDrop?.(draggedItems);
155
- }),
180
+ },
156
181
 
157
- onDrop: item.getMemoizedProp("dnd/onDrop", () => (e) => {
182
+ onDrop: (e: DragEvent) => {
158
183
  const dataRef = tree.getDataRef<DndDataRef>();
159
184
  const target = getDropTarget(e, item, tree);
160
185
 
@@ -171,19 +196,19 @@ export const dragAndDropFeature: FeatureImplementation<
171
196
 
172
197
  if (draggedItems) {
173
198
  config.onDrop?.(draggedItems, target);
174
- } else {
199
+ } else if (e.dataTransfer) {
175
200
  config.onDropForeignDragObject?.(e.dataTransfer, target);
176
201
  }
177
202
  // TODO rebuild tree?
178
- }),
203
+ },
179
204
  }),
180
205
 
181
- isDropTarget: () => {
206
+ isDropTarget: ({ tree, item }) => {
182
207
  const target = tree.getDropTarget();
183
208
  return target ? target.item.getId() === item.getId() : false;
184
209
  },
185
210
 
186
- isDropTargetAbove: () => {
211
+ isDropTargetAbove: ({ tree, item }) => {
187
212
  const target = tree.getDropTarget();
188
213
 
189
214
  if (
@@ -195,7 +220,7 @@ export const dragAndDropFeature: FeatureImplementation<
195
220
  return target.childIndex === item.getItemMeta().posInSet;
196
221
  },
197
222
 
198
- isDropTargetBelow: () => {
223
+ isDropTargetBelow: ({ tree, item }) => {
199
224
  const target = tree.getDropTarget();
200
225
 
201
226
  if (
@@ -207,8 +232,8 @@ export const dragAndDropFeature: FeatureImplementation<
207
232
  return target.childIndex - 1 === item.getItemMeta().posInSet;
208
233
  },
209
234
 
210
- isDraggingOver: () => {
235
+ isDraggingOver: ({ tree, item }) => {
211
236
  return tree.getState().dnd?.draggingOverItem?.getId() === item.getId();
212
237
  },
213
- }),
238
+ },
214
239
  };
@@ -2,19 +2,20 @@ import { ItemInstance, SetStateFn } from "../../types/core";
2
2
 
3
3
  export type DndDataRef = {
4
4
  lastDragCode?: string;
5
+ lastAllowDrop?: boolean;
5
6
  };
6
7
 
7
8
  export type DndState<T> = {
8
- draggedItems?: ItemInstance<T>[]; // TODO not used anymore?
9
+ draggedItems?: ItemInstance<T>[];
9
10
  draggingOverItem?: ItemInstance<T>;
10
11
  dragTarget?: DropTarget<T>;
11
12
  };
12
13
 
13
14
  export type DragLineData = {
14
- intend: number;
15
+ indent: number;
15
16
  top: number;
16
17
  left: number;
17
- right: number;
18
+ width: number;
18
19
  };
19
20
 
20
21
  export type DropTarget<T> =
@@ -22,11 +23,15 @@ export type DropTarget<T> =
22
23
  item: ItemInstance<T>;
23
24
  childIndex: number;
24
25
  insertionIndex: number;
26
+ dragLineIndex: number;
27
+ dragLineLevel: number;
25
28
  }
26
29
  | {
27
- item: ItemInstance<T>;
30
+ item: ItemInstance<T>; // TODO just omit values instead of nulls; or maybe just make it union of dropTarget+itemInstance?
28
31
  childIndex: null;
29
32
  insertionIndex: null;
33
+ dragLineIndex: null;
34
+ dragLineLevel: null;
30
35
  };
31
36
 
32
37
  export enum DropTargetPosition {
@@ -40,16 +45,21 @@ export type DragAndDropFeatureDef<T> = {
40
45
  dnd?: DndState<T> | null;
41
46
  };
42
47
  config: {
43
- setDndState?: SetStateFn<DndState<T> | null>;
48
+ setDndState?: SetStateFn<DndState<T> | undefined | null>;
44
49
 
45
- topLinePercentage?: number;
46
- bottomLinePercentage?: number;
47
- canDropInbetween?: boolean;
50
+ /** Defines the size of the area at the top and bottom of an item where, when an item is dropped, the item willö
51
+ * be placed above or below the item within the same parent, as opposed to being placed inside the item.
52
+ * If `canReorder` is `false`, this is ignored. */
53
+ reorderAreaPercentage?: number;
54
+ canReorder?: boolean;
48
55
 
56
+ // TODO better document difference to canDrag(), or unify both
49
57
  isItemDraggable?: (item: ItemInstance<T>) => boolean;
50
58
  canDrag?: (items: ItemInstance<T>[]) => boolean;
51
59
  canDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => boolean;
52
60
 
61
+ indent?: number;
62
+
53
63
  createForeignDragObject?: (items: ItemInstance<T>[]) => {
54
64
  format: string;
55
65
  data: any;
@@ -78,11 +88,15 @@ export type DragAndDropFeatureDef<T> = {
78
88
  treeInstance: {
79
89
  getDropTarget: () => DropTarget<T> | null;
80
90
  getDragLineData: () => DragLineData | null;
91
+ getDragLineStyle: (
92
+ topOffset?: number,
93
+ leftOffset?: number,
94
+ ) => Record<string, any>;
81
95
  };
82
96
  itemInstance: {
83
97
  isDropTarget: () => boolean;
84
- isDropTargetAbove: () => boolean;
85
- isDropTargetBelow: () => boolean;
98
+ isDropTargetAbove: () => boolean; // TODO still correct?
99
+ isDropTargetBelow: () => boolean; // TODO still correct?
86
100
  isDraggingOver: () => boolean;
87
101
  };
88
102
  hotkeys: never;
@@ -1,13 +1,30 @@
1
1
  import { ItemInstance, TreeInstance } from "../../types/core";
2
- import { DropTarget, DropTargetPosition } from "./types";
3
-
4
- export const getDragCode = ({ item, childIndex }: DropTarget<any>) =>
5
- `${item.getId()}__${childIndex ?? "none"}`;
6
-
7
- export const getDropOffset = (e: any, item: ItemInstance<any>): number => {
8
- const bb = item.getElement()?.getBoundingClientRect();
9
- return bb ? (e.pageY - bb.top) / bb.height : 0.5;
10
- };
2
+ import { DropTarget } from "./types";
3
+
4
+ enum ItemDropCategory {
5
+ Item,
6
+ ExpandedFolder,
7
+ LastInGroup,
8
+ }
9
+
10
+ enum PlacementType {
11
+ ReorderAbove,
12
+ ReorderBelow,
13
+ MakeChild,
14
+ Reparent,
15
+ }
16
+
17
+ type TargetPlacement =
18
+ | {
19
+ type:
20
+ | PlacementType.ReorderAbove
21
+ | PlacementType.ReorderBelow
22
+ | PlacementType.MakeChild;
23
+ }
24
+ | {
25
+ type: PlacementType.Reparent;
26
+ reparentLevel: number;
27
+ };
11
28
 
12
29
  export const canDrop = (
13
30
  dataTransfer: DataTransfer | null,
@@ -21,6 +38,17 @@ export const canDrop = (
21
38
  return false;
22
39
  }
23
40
 
41
+ if (
42
+ draggedItems &&
43
+ draggedItems.some(
44
+ (draggedItem) =>
45
+ target.item.getId() === draggedItem.getId() ||
46
+ target.item.isDescendentOf(draggedItem.getId()),
47
+ )
48
+ ) {
49
+ return false;
50
+ }
51
+
24
52
  if (
25
53
  !draggedItems &&
26
54
  dataTransfer &&
@@ -32,79 +60,192 @@ export const canDrop = (
32
60
  return true;
33
61
  };
34
62
 
35
- const getDropTargetPosition = (
36
- offset: number,
37
- topLinePercentage: number,
38
- bottomLinePercentage: number,
39
- ) => {
40
- if (offset < topLinePercentage) {
41
- return DropTargetPosition.Top;
63
+ const getItemDropCategory = (item: ItemInstance<any>) => {
64
+ if (item.isExpanded()) {
65
+ return ItemDropCategory.ExpandedFolder;
42
66
  }
43
- if (offset > bottomLinePercentage) {
44
- return DropTargetPosition.Bottom;
67
+
68
+ const parent = item.getParent();
69
+ if (parent && item.getIndexInParent() === parent.getItemMeta().setSize - 1) {
70
+ return ItemDropCategory.LastInGroup;
45
71
  }
46
- return DropTargetPosition.Item;
72
+
73
+ return ItemDropCategory.Item;
74
+ };
75
+
76
+ const getTargetPlacement = (
77
+ e: any,
78
+ item: ItemInstance<any>,
79
+ tree: TreeInstance<any>,
80
+ canMakeChild: boolean,
81
+ ): TargetPlacement => {
82
+ const config = tree.getConfig();
83
+
84
+ if (!config.canReorder) {
85
+ return canMakeChild
86
+ ? { type: PlacementType.MakeChild }
87
+ : { type: PlacementType.ReorderBelow };
88
+ }
89
+
90
+ 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;
93
+ const targetDropCategory = getItemDropCategory(item);
94
+ const reorderAreaPercentage = !canMakeChild
95
+ ? 0.5
96
+ : config.reorderAreaPercentage ?? 0.3;
97
+ const indent = config.indent ?? 20;
98
+ const makeChildType = canMakeChild
99
+ ? PlacementType.MakeChild
100
+ : PlacementType.ReorderBelow;
101
+
102
+ if (targetDropCategory === ItemDropCategory.ExpandedFolder) {
103
+ if (topPercent < reorderAreaPercentage) {
104
+ return { type: PlacementType.ReorderAbove };
105
+ }
106
+ return { type: makeChildType };
107
+ }
108
+
109
+ if (targetDropCategory === ItemDropCategory.LastInGroup) {
110
+ if (leftPixels < item.getItemMeta().level * indent) {
111
+ if (topPercent < 0.5) {
112
+ return { type: PlacementType.ReorderAbove };
113
+ }
114
+ return {
115
+ type: PlacementType.Reparent,
116
+ reparentLevel: Math.floor(leftPixels / indent),
117
+ };
118
+ }
119
+ // if not at left of item area, treat as if it was a normal item
120
+ }
121
+
122
+ // targetDropCategory === ItemDropCategory.Item
123
+ if (topPercent < reorderAreaPercentage) {
124
+ return { type: PlacementType.ReorderAbove };
125
+ }
126
+ if (topPercent > 1 - reorderAreaPercentage) {
127
+ return { type: PlacementType.ReorderBelow };
128
+ }
129
+ return { type: makeChildType };
130
+ };
131
+
132
+ export const getDragCode = (
133
+ e: any,
134
+ item: ItemInstance<any>,
135
+ tree: TreeInstance<any>,
136
+ ) => {
137
+ const placement = getTargetPlacement(e, item, tree, true);
138
+ return [
139
+ item.getId(),
140
+ placement.type,
141
+ placement.type === PlacementType.Reparent ? placement.reparentLevel : 0,
142
+ ].join("__");
143
+ };
144
+
145
+ const getNthParent = (
146
+ item: ItemInstance<any>,
147
+ n: number,
148
+ ): ItemInstance<any> => {
149
+ if (n === item.getItemMeta().level) {
150
+ return item;
151
+ }
152
+ return getNthParent(item.getParent()!, n);
47
153
  };
48
154
 
49
155
  export const getDropTarget = (
50
156
  e: any,
51
157
  item: ItemInstance<any>,
52
158
  tree: TreeInstance<any>,
53
- canDropInbetween = tree.getConfig().canDropInbetween,
159
+ canReorder = tree.getConfig().canReorder,
54
160
  ): DropTarget<any> => {
55
- const config = tree.getConfig();
56
161
  const draggedItems = tree.getState().dnd?.draggedItems ?? [];
57
- const itemTarget = { item, childIndex: null, insertionIndex: null };
58
- const parentTarget = {
59
- item: item.getParent(),
162
+ const itemMeta = item.getItemMeta();
163
+ const parent = item.getParent();
164
+ const itemTarget: DropTarget<any> = {
165
+ item,
60
166
  childIndex: null,
61
167
  insertionIndex: null,
168
+ dragLineIndex: null,
169
+ dragLineLevel: null,
62
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;
180
+ const canBecomeSibling =
181
+ parentTarget && canDrop(e.dataTransfer, parentTarget, tree);
182
+
183
+ const canMakeChild = canDrop(e.dataTransfer, itemTarget, tree);
184
+ const placement = getTargetPlacement(e, item, tree, canMakeChild);
63
185
 
64
- if (!canDropInbetween) {
65
- if (!canDrop(e.dataTransfer, parentTarget, tree)) {
66
- return getDropTarget(e, item.getParent(), tree, false);
67
- }
68
- return itemTarget;
186
+ if (
187
+ !canReorder &&
188
+ parent &&
189
+ canBecomeSibling &&
190
+ placement.type !== PlacementType.MakeChild
191
+ ) {
192
+ return parentTarget;
69
193
  }
70
194
 
71
- const canDropInside = canDrop(e.dataTransfer, itemTarget, tree);
72
-
73
- const offset = getDropOffset(e, item);
195
+ 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);
198
+ }
74
199
 
75
- const pos = canDropInside
76
- ? getDropTargetPosition(
77
- offset,
78
- config.topLinePercentage ?? 0.3,
79
- config.bottomLinePercentage ?? 0.7,
80
- )
81
- : getDropTargetPosition(offset, 0.5, 0.5);
200
+ if (!parent) {
201
+ // Shouldn't happen, but if dropped "next" to root item, just drop it inside
202
+ return itemTarget;
203
+ }
82
204
 
83
- if (pos === DropTargetPosition.Item) {
205
+ if (placement.type === PlacementType.MakeChild) {
84
206
  return itemTarget;
85
207
  }
86
208
 
87
- if (!canDrop(e.dataTransfer, parentTarget, tree)) {
88
- return getDropTarget(e, item.getParent(), tree, false);
209
+ if (!canBecomeSibling) {
210
+ return getDropTarget(e, parent, tree, false);
89
211
  }
90
212
 
91
- const childIndex =
92
- item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1);
213
+ 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
+ };
227
+ }
93
228
 
94
- const numberOfDragItemsBeforeTarget = item
95
- .getParent()
96
- .getChildren()
97
- .slice(0, childIndex)
98
- .reduce(
99
- (counter, child) =>
100
- child && draggedItems?.some((i) => i.getId() === child.getId())
101
- ? ++counter
102
- : counter,
103
- 0,
104
- );
229
+ const maybeAddOneForBelow =
230
+ placement.type === PlacementType.ReorderAbove ? 0 : 1;
231
+ const childIndex = item.getIndexInParent() + maybeAddOneForBelow;
232
+
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;
105
244
 
106
245
  return {
107
- item: item.getParent(),
246
+ item: parent,
247
+ dragLineIndex: itemMeta.index + maybeAddOneForBelow,
248
+ dragLineLevel: itemMeta.level,
108
249
  childIndex,
109
250
  // TODO performance could be improved by computing this only when dragcode changed
110
251
  insertionIndex: childIndex - numberOfDragItemsBeforeTarget,