@headless-tree/core 0.0.10 → 0.0.11

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 (114) hide show
  1. package/CHANGELOG.md +6 -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 +27 -0
  6. package/lib/cjs/core/create-tree.js +55 -36
  7. package/lib/cjs/features/async-data-loader/feature.js +37 -23
  8. package/lib/cjs/features/async-data-loader/types.d.ts +2 -1
  9. package/lib/cjs/features/drag-and-drop/feature.js +64 -32
  10. package/lib/cjs/features/drag-and-drop/types.d.ts +13 -4
  11. package/lib/cjs/features/drag-and-drop/utils.d.ts +1 -2
  12. package/lib/cjs/features/drag-and-drop/utils.js +140 -37
  13. package/lib/cjs/features/expand-all/feature.js +12 -6
  14. package/lib/cjs/features/main/types.d.ts +8 -2
  15. package/lib/cjs/features/renaming/feature.js +33 -18
  16. package/lib/cjs/features/renaming/types.d.ts +1 -1
  17. package/lib/cjs/features/search/feature.js +38 -24
  18. package/lib/cjs/features/search/types.d.ts +0 -1
  19. package/lib/cjs/features/selection/feature.js +23 -14
  20. package/lib/cjs/features/sync-data-loader/feature.js +7 -2
  21. package/lib/cjs/features/tree/feature.d.ts +2 -1
  22. package/lib/cjs/features/tree/feature.js +85 -63
  23. package/lib/cjs/features/tree/types.d.ts +5 -3
  24. package/lib/cjs/index.d.ts +3 -1
  25. package/lib/cjs/index.js +2 -1
  26. package/lib/cjs/test-utils/test-tree-do.d.ts +23 -0
  27. package/lib/cjs/test-utils/test-tree-do.js +99 -0
  28. package/lib/cjs/test-utils/test-tree-expect.d.ts +15 -0
  29. package/lib/cjs/test-utils/test-tree-expect.js +62 -0
  30. package/lib/cjs/test-utils/test-tree.d.ts +47 -0
  31. package/lib/cjs/test-utils/test-tree.js +195 -0
  32. package/lib/cjs/types/core.d.ts +31 -15
  33. package/lib/cjs/utilities/errors.d.ts +1 -0
  34. package/lib/cjs/utilities/errors.js +5 -0
  35. package/lib/cjs/utilities/insert-items-at-target.js +10 -3
  36. package/lib/cjs/utilities/remove-items-from-parents.js +14 -8
  37. package/lib/cjs/utils.d.ts +3 -3
  38. package/lib/cjs/utils.js +6 -6
  39. package/lib/esm/core/build-proxified-instance.d.ts +2 -0
  40. package/lib/esm/core/build-proxified-instance.js +54 -0
  41. package/lib/esm/core/build-static-instance.d.ts +2 -0
  42. package/lib/esm/core/build-static-instance.js +23 -0
  43. package/lib/esm/core/create-tree.js +55 -36
  44. package/lib/esm/features/async-data-loader/feature.js +37 -23
  45. package/lib/esm/features/async-data-loader/types.d.ts +2 -1
  46. package/lib/esm/features/drag-and-drop/feature.js +64 -32
  47. package/lib/esm/features/drag-and-drop/types.d.ts +13 -4
  48. package/lib/esm/features/drag-and-drop/utils.d.ts +1 -2
  49. package/lib/esm/features/drag-and-drop/utils.js +138 -34
  50. package/lib/esm/features/expand-all/feature.js +12 -6
  51. package/lib/esm/features/main/types.d.ts +8 -2
  52. package/lib/esm/features/renaming/feature.js +33 -18
  53. package/lib/esm/features/renaming/types.d.ts +1 -1
  54. package/lib/esm/features/search/feature.js +38 -24
  55. package/lib/esm/features/search/types.d.ts +0 -1
  56. package/lib/esm/features/selection/feature.js +23 -14
  57. package/lib/esm/features/sync-data-loader/feature.js +7 -2
  58. package/lib/esm/features/tree/feature.d.ts +2 -1
  59. package/lib/esm/features/tree/feature.js +86 -64
  60. package/lib/esm/features/tree/types.d.ts +5 -3
  61. package/lib/esm/index.d.ts +3 -1
  62. package/lib/esm/index.js +2 -1
  63. package/lib/esm/test-utils/test-tree-do.d.ts +23 -0
  64. package/lib/esm/test-utils/test-tree-do.js +95 -0
  65. package/lib/esm/test-utils/test-tree-expect.d.ts +15 -0
  66. package/lib/esm/test-utils/test-tree-expect.js +58 -0
  67. package/lib/esm/test-utils/test-tree.d.ts +47 -0
  68. package/lib/esm/test-utils/test-tree.js +191 -0
  69. package/lib/esm/types/core.d.ts +31 -15
  70. package/lib/esm/utilities/errors.d.ts +1 -0
  71. package/lib/esm/utilities/errors.js +1 -0
  72. package/lib/esm/utilities/insert-items-at-target.js +10 -3
  73. package/lib/esm/utilities/remove-items-from-parents.js +14 -8
  74. package/lib/esm/utils.d.ts +3 -3
  75. package/lib/esm/utils.js +3 -3
  76. package/package.json +7 -3
  77. package/src/core/build-proxified-instance.ts +115 -0
  78. package/src/core/build-static-instance.ts +28 -0
  79. package/src/core/create-tree.ts +60 -62
  80. package/src/features/async-data-loader/async-data-loader.spec.ts +143 -0
  81. package/src/features/async-data-loader/feature.ts +33 -31
  82. package/src/features/async-data-loader/types.ts +3 -1
  83. package/src/features/drag-and-drop/drag-and-drop.spec.ts +716 -0
  84. package/src/features/drag-and-drop/feature.ts +109 -85
  85. package/src/features/drag-and-drop/types.ts +21 -7
  86. package/src/features/drag-and-drop/utils.ts +196 -55
  87. package/src/features/expand-all/expand-all.spec.ts +52 -0
  88. package/src/features/expand-all/feature.ts +8 -12
  89. package/src/features/hotkeys-core/feature.ts +1 -1
  90. package/src/features/main/types.ts +14 -1
  91. package/src/features/renaming/feature.ts +30 -29
  92. package/src/features/renaming/renaming.spec.ts +125 -0
  93. package/src/features/renaming/types.ts +1 -1
  94. package/src/features/search/feature.ts +34 -38
  95. package/src/features/search/search.spec.ts +115 -0
  96. package/src/features/search/types.ts +0 -1
  97. package/src/features/selection/feature.ts +29 -30
  98. package/src/features/selection/selection.spec.ts +220 -0
  99. package/src/features/sync-data-loader/feature.ts +8 -11
  100. package/src/features/tree/feature.ts +82 -87
  101. package/src/features/tree/tree.spec.ts +515 -0
  102. package/src/features/tree/types.ts +5 -3
  103. package/src/index.ts +4 -1
  104. package/src/test-utils/test-tree-do.ts +136 -0
  105. package/src/test-utils/test-tree-expect.ts +86 -0
  106. package/src/test-utils/test-tree.ts +217 -0
  107. package/src/types/core.ts +92 -33
  108. package/src/utilities/errors.ts +2 -0
  109. package/src/utilities/insert-items-at-target.ts +10 -3
  110. package/src/utilities/remove-items-from-parents.ts +15 -10
  111. package/src/utils.spec.ts +89 -0
  112. package/src/utils.ts +6 -6
  113. package/tsconfig.json +1 -0
  114. package/vitest.config.ts +6 -0
@@ -6,18 +6,19 @@ export const asyncDataLoaderFeature = {
6
6
  stateHandlerNames: {
7
7
  loadingItems: "setLoadingItems",
8
8
  },
9
- createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: (itemId) => {
9
+ treeInstance: {
10
+ retrieveItemData: ({ tree }, itemId) => {
10
11
  var _a, _b, _c, _d, _e;
11
12
  var _f, _g;
12
- const config = instance.getConfig();
13
- const dataRef = instance.getDataRef();
13
+ const config = tree.getConfig();
14
+ const dataRef = tree.getDataRef();
14
15
  (_a = (_f = dataRef.current).itemData) !== null && _a !== void 0 ? _a : (_f.itemData = {});
15
16
  (_b = (_g = dataRef.current).childrenIds) !== null && _b !== void 0 ? _b : (_g.childrenIds = {});
16
17
  if (dataRef.current.itemData[itemId]) {
17
18
  return dataRef.current.itemData[itemId];
18
19
  }
19
- if (!instance.getState().loadingItems.includes(itemId)) {
20
- instance.applySubStateUpdate("loadingItems", (loadingItems) => [
20
+ if (!tree.getState().loadingItems.includes(itemId)) {
21
+ tree.applySubStateUpdate("loadingItems", (loadingItems) => [
21
22
  ...loadingItems,
22
23
  itemId,
23
24
  ]);
@@ -25,24 +26,25 @@ export const asyncDataLoaderFeature = {
25
26
  var _a;
26
27
  dataRef.current.itemData[itemId] = item;
27
28
  (_a = config.onLoadedItem) === null || _a === void 0 ? void 0 : _a.call(config, itemId, item);
28
- instance.applySubStateUpdate("loadingItems", (loadingItems) => loadingItems.filter((id) => id !== itemId));
29
+ tree.applySubStateUpdate("loadingItems", (loadingItems) => loadingItems.filter((id) => id !== itemId));
29
30
  });
30
31
  }
31
32
  return (_e = (_d = config.createLoadingItemData) === null || _d === void 0 ? void 0 : _d.call(config)) !== null && _e !== void 0 ? _e : null;
32
- }, retrieveChildrenIds: (itemId) => {
33
+ },
34
+ retrieveChildrenIds: ({ tree }, itemId) => {
33
35
  var _a, _b, _c, _d, _e;
34
36
  var _f, _g;
35
- const config = instance.getConfig();
36
- const dataRef = instance.getDataRef();
37
+ const config = tree.getConfig();
38
+ const dataRef = tree.getDataRef();
37
39
  (_a = (_f = dataRef.current).itemData) !== null && _a !== void 0 ? _a : (_f.itemData = {});
38
40
  (_b = (_g = dataRef.current).childrenIds) !== null && _b !== void 0 ? _b : (_g.childrenIds = {});
39
41
  if (dataRef.current.childrenIds[itemId]) {
40
42
  return dataRef.current.childrenIds[itemId];
41
43
  }
42
- if (instance.getState().loadingItems.includes(itemId)) {
44
+ if (tree.getState().loadingItems.includes(itemId)) {
43
45
  return [];
44
46
  }
45
- instance.applySubStateUpdate("loadingItems", (loadingItems) => [
47
+ tree.applySubStateUpdate("loadingItems", (loadingItems) => [
46
48
  ...loadingItems,
47
49
  itemId,
48
50
  ]);
@@ -56,8 +58,8 @@ export const asyncDataLoaderFeature = {
56
58
  const childrenIds = children.map(({ id }) => id);
57
59
  dataRef.current.childrenIds[itemId] = childrenIds;
58
60
  (_b = config.onLoadedChildren) === null || _b === void 0 ? void 0 : _b.call(config, itemId, childrenIds);
59
- instance.applySubStateUpdate("loadingItems", (loadingItems) => loadingItems.filter((id) => id !== itemId));
60
- instance.rebuildTree();
61
+ tree.applySubStateUpdate("loadingItems", (loadingItems) => loadingItems.filter((id) => id !== itemId));
62
+ tree.rebuildTree();
61
63
  });
62
64
  }
63
65
  else {
@@ -65,21 +67,33 @@ export const asyncDataLoaderFeature = {
65
67
  var _a;
66
68
  dataRef.current.childrenIds[itemId] = childrenIds;
67
69
  (_a = config.onLoadedChildren) === null || _a === void 0 ? void 0 : _a.call(config, itemId, childrenIds);
68
- instance.applySubStateUpdate("loadingItems", (loadingItems) => loadingItems.filter((id) => id !== itemId));
69
- instance.rebuildTree();
70
+ tree.applySubStateUpdate("loadingItems", (loadingItems) => loadingItems.filter((id) => id !== itemId));
71
+ tree.rebuildTree();
70
72
  });
71
73
  }
72
74
  return [];
73
- }, invalidateItemData: (itemId) => {
75
+ },
76
+ invalidateItemData: ({ tree }, itemId) => {
74
77
  var _a;
75
- const dataRef = instance.getDataRef();
78
+ const dataRef = tree.getDataRef();
76
79
  (_a = dataRef.current.itemData) === null || _a === void 0 ? true : delete _a[itemId];
77
- instance.retrieveItemData(itemId);
78
- }, invalidateChildrenIds: (itemId) => {
80
+ tree.retrieveItemData(itemId);
81
+ },
82
+ invalidateChildrenIds: ({ tree }, itemId) => {
79
83
  var _a;
80
- const dataRef = instance.getDataRef();
84
+ const dataRef = tree.getDataRef();
81
85
  (_a = dataRef.current.childrenIds) === null || _a === void 0 ? true : delete _a[itemId];
82
- instance.retrieveChildrenIds(itemId);
83
- } })),
84
- createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { isLoading: () => tree.getState().loadingItems.includes(item.getItemMeta().itemId), invalidateItemData: () => tree.invalidateItemData(item.getItemMeta().itemId), invalidateChildrenIds: () => tree.invalidateChildrenIds(item.getItemMeta().itemId) })),
86
+ tree.retrieveChildrenIds(itemId);
87
+ },
88
+ },
89
+ itemInstance: {
90
+ isLoading: ({ tree, item }) => tree.getState().loadingItems.includes(item.getItemMeta().itemId),
91
+ invalidateItemData: ({ tree, item }) => tree.invalidateItemData(item.getItemMeta().itemId),
92
+ invalidateChildrenIds: ({ tree, item }) => tree.invalidateChildrenIds(item.getItemMeta().itemId),
93
+ updateCachedChildrenIds: ({ tree, itemId }, childrenIds) => {
94
+ const dataRef = tree.getDataRef();
95
+ dataRef.current.childrenIds[itemId] = childrenIds;
96
+ tree.rebuildTree();
97
+ },
98
+ },
85
99
  };
@@ -35,7 +35,8 @@ export type AsyncDataLoaderFeatureDef<T> = {
35
35
  itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
36
36
  invalidateItemData: () => void;
37
37
  invalidateChildrenIds: () => void;
38
- isLoading: () => void;
38
+ updateCachedChildrenIds: (childrenIds: string[]) => void;
39
+ isLoading: () => boolean;
39
40
  };
40
41
  hotkeys: SyncDataLoaderFeatureDef<T>["hotkeys"];
41
42
  };
@@ -3,45 +3,64 @@ import { makeStateUpdater } from "../../utils";
3
3
  export const dragAndDropFeature = {
4
4
  key: "dragAndDrop",
5
5
  deps: ["selection"],
6
- getDefaultConfig: (defaultConfig, tree) => (Object.assign({ canDrop: (_, target) => target.item.isFolder(), canDropForeignDragObject: () => false, setDndState: makeStateUpdater("dnd", tree) }, defaultConfig)),
6
+ getDefaultConfig: (defaultConfig, tree) => (Object.assign({ canDrop: (_, target) => target.item.isFolder(), canDropForeignDragObject: () => false, setDndState: makeStateUpdater("dnd", tree), canDropInbetween: true }, defaultConfig)),
7
7
  stateHandlerNames: {
8
8
  dnd: "setDndState",
9
9
  },
10
- createTreeInstance: (prev, tree) => (Object.assign(Object.assign({}, prev), { getDropTarget: () => {
10
+ treeInstance: {
11
+ getDropTarget: ({ tree }) => {
11
12
  var _a, _b;
12
13
  return (_b = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.dragTarget) !== null && _b !== void 0 ? _b : null;
13
- }, getDragLineData: () => {
14
+ },
15
+ getDragLineData: ({ tree }) => {
14
16
  var _a, _b, _c, _d, _e;
17
+ // TODO doesnt work if scrolled down!
15
18
  const target = tree.getDropTarget();
16
- const intend = ((_a = target === null || target === void 0 ? void 0 : target.item.getItemMeta().level) !== null && _a !== void 0 ? _a : 0) + 1;
19
+ const indent = ((_a = target === null || target === void 0 ? void 0 : target.item.getItemMeta().level) !== null && _a !== void 0 ? _a : 0) + 1; // TODO rename to indent
17
20
  if (!target || target.childIndex === null)
18
21
  return null;
19
- const children = target.item.getChildren();
20
- if (target.childIndex === children.length) {
21
- const bb = (_c = (_b = children[target.childIndex - 1]) === null || _b === void 0 ? void 0 : _b.getElement()) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect();
22
+ const leftOffset = target.dragLineLevel * ((_b = tree.getConfig().indent) !== null && _b !== void 0 ? _b : 1);
23
+ const targetItem = tree.getItems()[target.dragLineIndex];
24
+ if (!targetItem) {
25
+ const bb = (_d = (_c = tree
26
+ .getItems()[target.dragLineIndex - 1]) === null || _c === void 0 ? void 0 : _c.getElement()) === null || _d === void 0 ? void 0 : _d.getBoundingClientRect();
22
27
  if (bb) {
23
28
  return {
24
- intend,
29
+ indent,
25
30
  top: bb.bottom,
26
- left: bb.left,
31
+ left: bb.left + leftOffset,
27
32
  right: bb.right,
28
33
  };
29
34
  }
30
35
  }
31
- const bb = (_e = (_d = children[target.childIndex]) === null || _d === void 0 ? void 0 : _d.getElement()) === null || _e === void 0 ? void 0 : _e.getBoundingClientRect();
36
+ const bb = (_e = targetItem.getElement()) === null || _e === void 0 ? void 0 : _e.getBoundingClientRect();
32
37
  if (bb) {
33
38
  return {
34
- intend,
39
+ indent,
35
40
  top: bb.top,
36
- left: bb.left,
41
+ left: bb.left + leftOffset,
37
42
  right: bb.right,
38
43
  };
39
44
  }
40
45
  return null;
41
- } })),
42
- createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { getProps: () => {
46
+ },
47
+ getDragLineStyle: ({ tree }, topOffset = -1, leftOffset = -8) => {
48
+ const dragLine = tree.getDragLineData();
49
+ return dragLine
50
+ ? {
51
+ top: `${dragLine.top + topOffset}px`,
52
+ left: `${dragLine.left + leftOffset}px`,
53
+ width: `${dragLine.right - dragLine.left - leftOffset}px`,
54
+ pointerEvents: "none", // important to prevent capturing drag events
55
+ }
56
+ : { display: "none" };
57
+ },
58
+ },
59
+ itemInstance: {
60
+ // TODO instead of individual getMemoizedProp calls, use a wrapped getMemoizedProps or something (getProps: () => getMemoized({...})
61
+ getProps: ({ tree, item, prev }) => {
43
62
  var _a, _b, _c;
44
- return (Object.assign(Object.assign({}, prev.getProps()), { draggable: (_c = (_b = (_a = tree.getConfig()).isItemDraggable) === null || _b === void 0 ? void 0 : _b.call(_a, item)) !== null && _c !== void 0 ? _c : true, onDragStart: item.getMemoizedProp("dnd/onDragStart", () => (e) => {
63
+ return (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { draggable: (_c = (_b = (_a = tree.getConfig()).isItemDraggable) === null || _b === void 0 ? void 0 : _b.call(_a, item)) !== null && _c !== void 0 ? _c : true, onDragStart: item.getMemoizedProp("dnd/onDragStart", () => (e) => {
45
64
  var _a, _b, _c;
46
65
  const selectedItems = tree.getSelectedItems();
47
66
  const items = selectedItems.includes(item) ? selectedItems : [item];
@@ -63,34 +82,42 @@ export const dragAndDropFeature = {
63
82
  });
64
83
  }), onDragOver: item.getMemoizedProp("dnd/onDragOver", () => (e) => {
65
84
  var _a, _b, _c;
66
- const target = getDropTarget(e, item, tree);
67
85
  const dataRef = tree.getDataRef();
68
- if (!((_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems) &&
69
- !((_c = (_b = tree.getConfig()).canDropForeignDragObject) === null || _c === void 0 ? void 0 : _c.call(_b, e.dataTransfer, target))) {
86
+ const nextDragCode = getDragCode(e, item, tree);
87
+ if (nextDragCode === dataRef.current.lastDragCode) {
88
+ if (dataRef.current.lastAllowDrop) {
89
+ e.preventDefault();
90
+ }
70
91
  return;
71
92
  }
72
- if (!canDrop(e.dataTransfer, target, tree)) {
93
+ dataRef.current.lastDragCode = nextDragCode;
94
+ const target = getDropTarget(e, item, tree);
95
+ if (!((_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems) &&
96
+ (!e.dataTransfer ||
97
+ !((_c = (_b = tree
98
+ .getConfig()).canDropForeignDragObject) === null || _c === void 0 ? void 0 : _c.call(_b, e.dataTransfer, target)))) {
99
+ dataRef.current.lastAllowDrop = false;
73
100
  return;
74
101
  }
75
- e.preventDefault();
76
- const nextDragCode = getDragCode(target);
77
- if (nextDragCode === dataRef.current.lastDragCode) {
102
+ if (!canDrop(e.dataTransfer, target, tree)) {
103
+ dataRef.current.lastAllowDrop = false;
78
104
  return;
79
105
  }
80
- dataRef.current.lastDragCode = nextDragCode;
81
106
  tree.applySubStateUpdate("dnd", (state) => (Object.assign(Object.assign({}, state), { dragTarget: target, draggingOverItem: item })));
107
+ dataRef.current.lastAllowDrop = true;
108
+ e.preventDefault();
82
109
  }), onDragLeave: item.getMemoizedProp("dnd/onDragLeave", () => () => {
83
110
  const dataRef = tree.getDataRef();
84
111
  dataRef.current.lastDragCode = "no-drag";
85
112
  tree.applySubStateUpdate("dnd", (state) => (Object.assign(Object.assign({}, state), { draggingOverItem: undefined, dragTarget: undefined })));
86
113
  }), onDragEnd: item.getMemoizedProp("dnd/onDragEnd", () => (e) => {
87
- var _a, _b, _c;
114
+ var _a, _b, _c, _d;
88
115
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
89
116
  tree.applySubStateUpdate("dnd", null);
90
- if (e.dataTransfer.dropEffect === "none" || !draggedItems) {
117
+ if (((_b = e.dataTransfer) === null || _b === void 0 ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
91
118
  return;
92
119
  }
93
- (_c = (_b = tree.getConfig()).onCompleteForeignDrop) === null || _c === void 0 ? void 0 : _c.call(_b, draggedItems);
120
+ (_d = (_c = tree.getConfig()).onCompleteForeignDrop) === null || _d === void 0 ? void 0 : _d.call(_c, draggedItems);
94
121
  }), onDrop: item.getMemoizedProp("dnd/onDrop", () => (e) => {
95
122
  var _a, _b, _c;
96
123
  const dataRef = tree.getDataRef();
@@ -106,30 +133,35 @@ export const dragAndDropFeature = {
106
133
  if (draggedItems) {
107
134
  (_b = config.onDrop) === null || _b === void 0 ? void 0 : _b.call(config, draggedItems, target);
108
135
  }
109
- else {
136
+ else if (e.dataTransfer) {
110
137
  (_c = config.onDropForeignDragObject) === null || _c === void 0 ? void 0 : _c.call(config, e.dataTransfer, target);
111
138
  }
112
139
  // TODO rebuild tree?
113
140
  }) }));
114
- }, isDropTarget: () => {
141
+ },
142
+ isDropTarget: ({ tree, item }) => {
115
143
  const target = tree.getDropTarget();
116
144
  return target ? target.item.getId() === item.getId() : false;
117
- }, isDropTargetAbove: () => {
145
+ },
146
+ isDropTargetAbove: ({ tree, item }) => {
118
147
  const target = tree.getDropTarget();
119
148
  if (!target ||
120
149
  target.childIndex === null ||
121
150
  target.item !== item.getParent())
122
151
  return false;
123
152
  return target.childIndex === item.getItemMeta().posInSet;
124
- }, isDropTargetBelow: () => {
153
+ },
154
+ isDropTargetBelow: ({ tree, item }) => {
125
155
  const target = tree.getDropTarget();
126
156
  if (!target ||
127
157
  target.childIndex === null ||
128
158
  target.item !== item.getParent())
129
159
  return false;
130
160
  return target.childIndex - 1 === item.getItemMeta().posInSet;
131
- }, isDraggingOver: () => {
161
+ },
162
+ isDraggingOver: ({ tree, item }) => {
132
163
  var _a, _b;
133
164
  return ((_b = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggingOverItem) === null || _b === void 0 ? void 0 : _b.getId()) === item.getId();
134
- } })),
165
+ },
166
+ },
135
167
  };
@@ -1,6 +1,7 @@
1
1
  import { ItemInstance, SetStateFn } from "../../types/core";
2
2
  export type DndDataRef = {
3
3
  lastDragCode?: string;
4
+ lastAllowDrop?: boolean;
4
5
  };
5
6
  export type DndState<T> = {
6
7
  draggedItems?: ItemInstance<T>[];
@@ -8,7 +9,7 @@ export type DndState<T> = {
8
9
  dragTarget?: DropTarget<T>;
9
10
  };
10
11
  export type DragLineData = {
11
- intend: number;
12
+ indent: number;
12
13
  top: number;
13
14
  left: number;
14
15
  right: number;
@@ -17,10 +18,14 @@ export type DropTarget<T> = {
17
18
  item: ItemInstance<T>;
18
19
  childIndex: number;
19
20
  insertionIndex: number;
21
+ dragLineIndex: number;
22
+ dragLineLevel: number;
20
23
  } | {
21
24
  item: ItemInstance<T>;
22
25
  childIndex: null;
23
26
  insertionIndex: null;
27
+ dragLineIndex: null;
28
+ dragLineLevel: null;
24
29
  };
25
30
  export declare enum DropTargetPosition {
26
31
  Top = "top",
@@ -32,13 +37,16 @@ export type DragAndDropFeatureDef<T> = {
32
37
  dnd?: DndState<T> | null;
33
38
  };
34
39
  config: {
35
- setDndState?: SetStateFn<DndState<T> | null>;
36
- topLinePercentage?: number;
37
- bottomLinePercentage?: number;
40
+ setDndState?: SetStateFn<DndState<T> | undefined | null>;
41
+ /** Defines the size of the area at the top and bottom of an item where, when an item is dropped, the item willö
42
+ * be placed above or below the item within the same parent, as opposed to being placed inside the item.
43
+ * If `canDropInbetween` is `false`, this is ignored. */
44
+ reorderAreaPercentage?: number;
38
45
  canDropInbetween?: boolean;
39
46
  isItemDraggable?: (item: ItemInstance<T>) => boolean;
40
47
  canDrag?: (items: ItemInstance<T>[]) => boolean;
41
48
  canDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => boolean;
49
+ indent?: number;
42
50
  createForeignDragObject?: (items: ItemInstance<T>[]) => {
43
51
  format: string;
44
52
  data: any;
@@ -58,6 +66,7 @@ export type DragAndDropFeatureDef<T> = {
58
66
  treeInstance: {
59
67
  getDropTarget: () => DropTarget<T> | null;
60
68
  getDragLineData: () => DragLineData | null;
69
+ getDragLineStyle: (topOffset?: number, leftOffset?: number) => Record<string, any>;
61
70
  };
62
71
  itemInstance: {
63
72
  isDropTarget: () => boolean;
@@ -1,6 +1,5 @@
1
1
  import { ItemInstance, TreeInstance } from "../../types/core";
2
2
  import { DropTarget } from "./types";
3
- export declare const getDragCode: ({ item, childIndex }: DropTarget<any>) => string;
4
- export declare const getDropOffset: (e: any, item: ItemInstance<any>) => number;
5
3
  export declare const canDrop: (dataTransfer: DataTransfer | null, target: DropTarget<any>, tree: TreeInstance<any>) => boolean;
4
+ export declare const getDragCode: (e: any, item: ItemInstance<any>, tree: TreeInstance<any>) => string;
6
5
  export declare const getDropTarget: (e: any, item: ItemInstance<any>, tree: TreeInstance<any>, canDropInbetween?: boolean | undefined) => DropTarget<any>;
@@ -1,10 +1,16 @@
1
- import { DropTargetPosition } from "./types";
2
- export const getDragCode = ({ item, childIndex }) => `${item.getId()}__${childIndex !== null && childIndex !== void 0 ? childIndex : "none"}`;
3
- export const getDropOffset = (e, item) => {
4
- var _a;
5
- const bb = (_a = item.getElement()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
6
- return bb ? (e.pageY - bb.top) / bb.height : 0.5;
7
- };
1
+ var ItemDropCategory;
2
+ (function (ItemDropCategory) {
3
+ ItemDropCategory[ItemDropCategory["Item"] = 0] = "Item";
4
+ ItemDropCategory[ItemDropCategory["ExpandedFolder"] = 1] = "ExpandedFolder";
5
+ ItemDropCategory[ItemDropCategory["LastInGroup"] = 2] = "LastInGroup";
6
+ })(ItemDropCategory || (ItemDropCategory = {}));
7
+ var PlacementType;
8
+ (function (PlacementType) {
9
+ PlacementType[PlacementType["ReorderAbove"] = 0] = "ReorderAbove";
10
+ PlacementType[PlacementType["ReorderBelow"] = 1] = "ReorderBelow";
11
+ PlacementType[PlacementType["MakeChild"] = 2] = "MakeChild";
12
+ PlacementType[PlacementType["Reparent"] = 3] = "Reparent";
13
+ })(PlacementType || (PlacementType = {}));
8
14
  export const canDrop = (dataTransfer, target, tree) => {
9
15
  var _a, _b, _c, _d;
10
16
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
@@ -12,6 +18,11 @@ export const canDrop = (dataTransfer, target, tree) => {
12
18
  if (draggedItems && !((_c = (_b = config.canDrop) === null || _b === void 0 ? void 0 : _b.call(config, draggedItems, target)) !== null && _c !== void 0 ? _c : true)) {
13
19
  return false;
14
20
  }
21
+ if (draggedItems &&
22
+ draggedItems.some((draggedItem) => target.item.getId() === draggedItem.getId() ||
23
+ target.item.isDescendentOf(draggedItem.getId()))) {
24
+ return false;
25
+ }
15
26
  if (!draggedItems &&
16
27
  dataTransfer &&
17
28
  !((_d = config.canDropForeignDragObject) === null || _d === void 0 ? void 0 : _d.call(config, dataTransfer, target))) {
@@ -19,52 +30,145 @@ export const canDrop = (dataTransfer, target, tree) => {
19
30
  }
20
31
  return true;
21
32
  };
22
- const getDropTargetPosition = (offset, topLinePercentage, bottomLinePercentage) => {
23
- if (offset < topLinePercentage) {
24
- return DropTargetPosition.Top;
33
+ const getItemDropCategory = (item) => {
34
+ if (item.isExpanded()) {
35
+ return ItemDropCategory.ExpandedFolder;
25
36
  }
26
- if (offset > bottomLinePercentage) {
27
- return DropTargetPosition.Bottom;
37
+ const parent = item.getParent();
38
+ if (parent && item.getIndexInParent() === parent.getItemMeta().setSize - 1) {
39
+ return ItemDropCategory.LastInGroup;
28
40
  }
29
- return DropTargetPosition.Item;
41
+ return ItemDropCategory.Item;
30
42
  };
31
- export const getDropTarget = (e, item, tree, canDropInbetween = tree.getConfig().canDropInbetween) => {
32
- var _a, _b, _c, _d;
43
+ const getTargetPlacement = (e, item, tree, canMakeChild) => {
44
+ var _a, _b, _c;
33
45
  const config = tree.getConfig();
46
+ if (!config.canDropInbetween) {
47
+ return canMakeChild
48
+ ? { type: PlacementType.MakeChild }
49
+ : { type: PlacementType.ReorderBelow };
50
+ }
51
+ const bb = (_a = item.getElement()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
52
+ const topPercent = bb ? (e.pageY - bb.top) / bb.height : 0.5;
53
+ const leftPixels = bb ? e.pageX - bb.left : 0;
54
+ const targetDropCategory = getItemDropCategory(item);
55
+ const reorderAreaPercentage = !canMakeChild
56
+ ? 0.5
57
+ : (_b = config.reorderAreaPercentage) !== null && _b !== void 0 ? _b : 0.3;
58
+ const indent = (_c = config.indent) !== null && _c !== void 0 ? _c : 20;
59
+ const makeChildType = canMakeChild
60
+ ? PlacementType.MakeChild
61
+ : PlacementType.ReorderBelow;
62
+ if (targetDropCategory === ItemDropCategory.ExpandedFolder) {
63
+ if (topPercent < reorderAreaPercentage) {
64
+ return { type: PlacementType.ReorderAbove };
65
+ }
66
+ return { type: makeChildType };
67
+ }
68
+ if (targetDropCategory === ItemDropCategory.LastInGroup) {
69
+ if (leftPixels < item.getItemMeta().level * indent) {
70
+ if (topPercent < 0.5) {
71
+ return { type: PlacementType.ReorderAbove };
72
+ }
73
+ return {
74
+ type: PlacementType.Reparent,
75
+ reparentLevel: Math.floor(leftPixels / indent),
76
+ };
77
+ }
78
+ // if not at left of item area, treat as if it was a normal item
79
+ }
80
+ // targetDropCategory === ItemDropCategory.Item
81
+ if (topPercent < reorderAreaPercentage) {
82
+ return { type: PlacementType.ReorderAbove };
83
+ }
84
+ if (topPercent > 1 - reorderAreaPercentage) {
85
+ return { type: PlacementType.ReorderBelow };
86
+ }
87
+ return { type: makeChildType };
88
+ };
89
+ export const getDragCode = (e, item, tree) => {
90
+ const placement = getTargetPlacement(e, item, tree, true);
91
+ return [
92
+ item.getId(),
93
+ placement.type,
94
+ placement.type === PlacementType.Reparent ? placement.reparentLevel : 0,
95
+ ].join("__");
96
+ };
97
+ const getNthParent = (item, n) => {
98
+ if (n === item.getItemMeta().level) {
99
+ return item;
100
+ }
101
+ return getNthParent(item.getParent(), n);
102
+ };
103
+ export const getDropTarget = (e, item, tree, canDropInbetween = tree.getConfig().canDropInbetween) => {
104
+ var _a, _b, _c;
34
105
  const draggedItems = (_b = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems) !== null && _b !== void 0 ? _b : [];
35
- const itemTarget = { item, childIndex: null, insertionIndex: null };
36
- const parentTarget = {
37
- item: item.getParent(),
106
+ const itemMeta = item.getItemMeta();
107
+ const parent = item.getParent();
108
+ const itemTarget = {
109
+ item,
38
110
  childIndex: null,
39
111
  insertionIndex: null,
112
+ dragLineIndex: null,
113
+ dragLineLevel: null,
40
114
  };
41
- if (!canDropInbetween) {
42
- if (!canDrop(e.dataTransfer, parentTarget, tree)) {
43
- return getDropTarget(e, item.getParent(), tree, false);
115
+ const parentTarget = parent
116
+ ? {
117
+ item: parent,
118
+ childIndex: null,
119
+ insertionIndex: null,
120
+ dragLineIndex: null,
121
+ dragLineLevel: null,
44
122
  }
123
+ : null;
124
+ const canBecomeSibling = parentTarget && canDrop(e.dataTransfer, parentTarget, tree);
125
+ const canMakeChild = canDrop(e.dataTransfer, itemTarget, tree);
126
+ const placement = getTargetPlacement(e, item, tree, canMakeChild);
127
+ if (!canDropInbetween &&
128
+ parent &&
129
+ canBecomeSibling &&
130
+ placement.type !== PlacementType.MakeChild) {
131
+ return parentTarget;
132
+ }
133
+ if (!canDropInbetween && parent && !canBecomeSibling) {
134
+ // TODO! this breaks in story DND/Can Drop. Maybe move this logic into a composable DropTargetStrategy[] ?
135
+ return getDropTarget(e, parent, tree, false);
136
+ }
137
+ if (!parent) {
138
+ // Shouldn't happen, but if dropped "next" to root item, just drop it inside
45
139
  return itemTarget;
46
140
  }
47
- const canDropInside = canDrop(e.dataTransfer, itemTarget, tree);
48
- const offset = getDropOffset(e, item);
49
- const pos = canDropInside
50
- ? getDropTargetPosition(offset, (_c = config.topLinePercentage) !== null && _c !== void 0 ? _c : 0.3, (_d = config.bottomLinePercentage) !== null && _d !== void 0 ? _d : 0.7)
51
- : getDropTargetPosition(offset, 0.5, 0.5);
52
- if (pos === DropTargetPosition.Item) {
141
+ if (placement.type === PlacementType.MakeChild) {
53
142
  return itemTarget;
54
143
  }
55
- if (!canDrop(e.dataTransfer, parentTarget, tree)) {
56
- return getDropTarget(e, item.getParent(), tree, false);
144
+ if (!canBecomeSibling) {
145
+ return getDropTarget(e, parent, tree, false);
146
+ }
147
+ if (placement.type === PlacementType.Reparent) {
148
+ const reparentedTarget = getNthParent(item, placement.reparentLevel - 1);
149
+ const targetItemAbove = getNthParent(item, placement.reparentLevel); // .getItemBelow()!;
150
+ const targetIndex = targetItemAbove.getIndexInParent() + 1;
151
+ // TODO possibly count items dragged out above the new target
152
+ return {
153
+ item: reparentedTarget,
154
+ childIndex: targetIndex,
155
+ insertionIndex: targetIndex,
156
+ dragLineIndex: itemMeta.index + 1,
157
+ dragLineLevel: placement.reparentLevel,
158
+ };
57
159
  }
58
- const childIndex = item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1);
59
- const numberOfDragItemsBeforeTarget = item
60
- .getParent()
160
+ const maybeAddOneForBelow = placement.type === PlacementType.ReorderAbove ? 0 : 1;
161
+ const childIndex = item.getIndexInParent() + maybeAddOneForBelow;
162
+ const numberOfDragItemsBeforeTarget = (_c = parent
61
163
  .getChildren()
62
164
  .slice(0, childIndex)
63
165
  .reduce((counter, child) => child && (draggedItems === null || draggedItems === void 0 ? void 0 : draggedItems.some((i) => i.getId() === child.getId()))
64
166
  ? ++counter
65
- : counter, 0);
167
+ : counter, 0)) !== null && _c !== void 0 ? _c : 0;
66
168
  return {
67
- item: item.getParent(),
169
+ item: parent,
170
+ dragLineIndex: itemMeta.index + maybeAddOneForBelow,
171
+ dragLineLevel: itemMeta.level,
68
172
  childIndex,
69
173
  // TODO performance could be improved by computing this only when dragcode changed
70
174
  insertionIndex: childIndex - numberOfDragItemsBeforeTarget,
@@ -10,13 +10,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { poll } from "../../utils";
11
11
  export const expandAllFeature = {
12
12
  key: "expand-all",
13
- createTreeInstance: (prev, tree) => (Object.assign(Object.assign({}, prev), { expandAll: (cancelToken) => __awaiter(void 0, void 0, void 0, function* () {
13
+ treeInstance: {
14
+ expandAll: (_a, cancelToken_1) => __awaiter(void 0, [_a, cancelToken_1], void 0, function* ({ tree }, cancelToken) {
14
15
  yield Promise.all(tree.getItems().map((item) => item.expandAll(cancelToken)));
15
- }), collapseAll: () => {
16
+ }),
17
+ collapseAll: ({ tree }) => {
16
18
  tree.applySubStateUpdate("expandedItems", []);
17
19
  tree.rebuildTree();
18
- } })),
19
- createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { expandAll: (cancelToken) => __awaiter(void 0, void 0, void 0, function* () {
20
+ },
21
+ },
22
+ itemInstance: {
23
+ expandAll: (_a, cancelToken_1) => __awaiter(void 0, [_a, cancelToken_1], void 0, function* ({ tree, item }, cancelToken) {
20
24
  if (cancelToken === null || cancelToken === void 0 ? void 0 : cancelToken.current) {
21
25
  return;
22
26
  }
@@ -29,10 +33,12 @@ export const expandAllFeature = {
29
33
  yield poll(() => !tree.getState().loadingItems.includes(child.getId()));
30
34
  yield (child === null || child === void 0 ? void 0 : child.expandAll(cancelToken));
31
35
  })));
32
- }), collapseAll: () => {
36
+ }),
37
+ collapseAll: ({ item }) => {
33
38
  for (const child of item.getChildren()) {
34
39
  child === null || child === void 0 ? void 0 : child.collapseAll();
35
40
  }
36
41
  item.collapse();
37
- } })),
42
+ },
43
+ },
38
44
  };
@@ -1,12 +1,18 @@
1
- import { FeatureImplementation, HotkeysConfig, ItemInstance, SetStateFn, TreeConfig, TreeState, Updater } from "../../types/core";
1
+ import { FeatureImplementation, HotkeysConfig, ItemInstance, SetStateFn, TreeConfig, TreeInstance, TreeState, Updater } from "../../types/core";
2
2
  import { ItemMeta } from "../tree/types";
3
+ export type InstanceTypeMap = {
4
+ itemInstance: ItemInstance<any>;
5
+ treeInstance: TreeInstance<any>;
6
+ };
7
+ export type InstanceBuilder = <T extends keyof InstanceTypeMap>(features: FeatureImplementation[], instanceType: T, buildOpts: (self: any) => any) => [instance: InstanceTypeMap[T], finalize: () => void];
3
8
  export type MainFeatureDef<T = any> = {
4
9
  state: {};
5
10
  config: {
6
11
  features?: FeatureImplementation<any>[];
7
12
  initialState?: Partial<TreeState<T>>;
8
13
  state?: Partial<TreeState<T>>;
9
- setState?: SetStateFn<TreeState<T>>;
14
+ setState?: SetStateFn<Partial<TreeState<T>>>;
15
+ instanceBuilder?: InstanceBuilder;
10
16
  };
11
17
  treeInstance: {
12
18
  /** @internal */