@headless-tree/core 0.0.5 → 0.0.7

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 (93) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/cjs/core/create-tree.js +34 -12
  3. package/lib/cjs/features/async-data-loader/feature.js +30 -22
  4. package/lib/cjs/features/drag-and-drop/feature.js +58 -18
  5. package/lib/cjs/features/drag-and-drop/types.d.ts +21 -1
  6. package/lib/cjs/features/drag-and-drop/utils.d.ts +1 -1
  7. package/lib/cjs/features/drag-and-drop/utils.js +34 -17
  8. package/lib/cjs/features/expand-all/feature.js +12 -10
  9. package/lib/cjs/features/expand-all/types.d.ts +2 -2
  10. package/lib/cjs/features/hotkeys-core/feature.js +0 -1
  11. package/lib/cjs/features/main/types.d.ts +3 -1
  12. package/lib/cjs/features/renaming/feature.js +10 -11
  13. package/lib/cjs/features/search/feature.js +21 -8
  14. package/lib/cjs/features/search/types.d.ts +1 -1
  15. package/lib/cjs/features/selection/feature.js +6 -5
  16. package/lib/cjs/features/sync-data-loader/feature.js +6 -1
  17. package/lib/cjs/features/sync-data-loader/types.d.ts +1 -1
  18. package/lib/cjs/features/tree/feature.js +23 -24
  19. package/lib/cjs/features/tree/types.d.ts +2 -2
  20. package/lib/cjs/index.d.ts +3 -1
  21. package/lib/cjs/index.js +3 -1
  22. package/lib/cjs/types/core.d.ts +3 -4
  23. package/lib/cjs/utilities/create-on-drop-handler.d.ts +3 -0
  24. package/lib/cjs/utilities/create-on-drop-handler.js +11 -0
  25. package/lib/cjs/utilities/insert-items-at-target.d.ts +3 -0
  26. package/lib/cjs/utilities/insert-items-at-target.js +24 -0
  27. package/lib/cjs/utilities/remove-items-from-parents.d.ts +2 -0
  28. package/lib/cjs/utilities/remove-items-from-parents.js +17 -0
  29. package/lib/cjs/utils.d.ts +1 -4
  30. package/lib/cjs/utils.js +1 -53
  31. package/lib/esm/core/create-tree.js +34 -12
  32. package/lib/esm/features/async-data-loader/feature.js +30 -22
  33. package/lib/esm/features/drag-and-drop/feature.js +58 -18
  34. package/lib/esm/features/drag-and-drop/types.d.ts +21 -1
  35. package/lib/esm/features/drag-and-drop/utils.d.ts +1 -1
  36. package/lib/esm/features/drag-and-drop/utils.js +35 -18
  37. package/lib/esm/features/expand-all/feature.js +12 -10
  38. package/lib/esm/features/expand-all/types.d.ts +2 -2
  39. package/lib/esm/features/hotkeys-core/feature.js +0 -1
  40. package/lib/esm/features/main/types.d.ts +3 -1
  41. package/lib/esm/features/renaming/feature.js +10 -11
  42. package/lib/esm/features/search/feature.js +21 -8
  43. package/lib/esm/features/search/types.d.ts +1 -1
  44. package/lib/esm/features/selection/feature.js +6 -5
  45. package/lib/esm/features/sync-data-loader/feature.js +6 -1
  46. package/lib/esm/features/sync-data-loader/types.d.ts +1 -1
  47. package/lib/esm/features/tree/feature.js +23 -24
  48. package/lib/esm/features/tree/types.d.ts +2 -2
  49. package/lib/esm/index.d.ts +3 -1
  50. package/lib/esm/index.js +3 -1
  51. package/lib/esm/types/core.d.ts +3 -4
  52. package/lib/esm/utilities/create-on-drop-handler.d.ts +3 -0
  53. package/lib/esm/utilities/create-on-drop-handler.js +7 -0
  54. package/lib/esm/utilities/insert-items-at-target.d.ts +3 -0
  55. package/lib/esm/utilities/insert-items-at-target.js +20 -0
  56. package/lib/esm/utilities/remove-items-from-parents.d.ts +2 -0
  57. package/lib/esm/utilities/remove-items-from-parents.js +13 -0
  58. package/lib/esm/utils.d.ts +1 -4
  59. package/lib/esm/utils.js +0 -50
  60. package/package.json +1 -1
  61. package/src/core/create-tree.ts +42 -9
  62. package/src/features/async-data-loader/feature.ts +15 -6
  63. package/src/features/drag-and-drop/feature.ts +75 -14
  64. package/src/features/drag-and-drop/types.ts +30 -5
  65. package/src/features/drag-and-drop/utils.ts +53 -24
  66. package/src/features/expand-all/feature.ts +10 -9
  67. package/src/features/expand-all/types.ts +2 -2
  68. package/src/features/hotkeys-core/feature.ts +0 -1
  69. package/src/features/main/types.ts +6 -0
  70. package/src/features/renaming/feature.ts +10 -7
  71. package/src/features/search/feature.ts +22 -6
  72. package/src/features/search/types.ts +1 -0
  73. package/src/features/selection/feature.ts +7 -3
  74. package/src/features/sync-data-loader/feature.ts +17 -3
  75. package/src/features/sync-data-loader/types.ts +1 -1
  76. package/src/features/tree/feature.ts +23 -22
  77. package/src/features/tree/types.ts +4 -2
  78. package/src/index.ts +4 -1
  79. package/src/types/core.ts +6 -5
  80. package/src/utilities/create-on-drop-handler.ts +14 -0
  81. package/src/utilities/insert-items-at-target.ts +30 -0
  82. package/src/utilities/remove-items-from-parents.ts +21 -0
  83. package/src/utils.ts +1 -69
  84. package/lib/cjs/data-adapters/nested-data-adapter.d.ts +0 -9
  85. package/lib/cjs/data-adapters/nested-data-adapter.js +0 -32
  86. package/lib/cjs/data-adapters/types.d.ts +0 -7
  87. package/lib/cjs/data-adapters/types.js +0 -2
  88. package/lib/esm/data-adapters/nested-data-adapter.d.ts +0 -9
  89. package/lib/esm/data-adapters/nested-data-adapter.js +0 -28
  90. package/lib/esm/data-adapters/types.d.ts +0 -7
  91. package/lib/esm/data-adapters/types.js +0 -1
  92. package/src/data-adapters/nested-data-adapter.ts +0 -48
  93. package/src/data-adapters/types.ts +0 -9
@@ -10,9 +10,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { makeStateUpdater, memo, poll } from "../../utils";
11
11
  export const treeFeature = {
12
12
  key: "tree",
13
- dependingFeatures: ["main"],
14
13
  getInitialState: (initialState) => (Object.assign({ expandedItems: [], focusedItem: null }, initialState)),
15
14
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setExpandedItems: makeStateUpdater("expandedItems", tree), setFocusedItem: makeStateUpdater("focusedItem", tree) }, defaultConfig)),
15
+ stateHandlerNames: {
16
+ expandedItems: "setExpandedItems",
17
+ focusedItem: "setFocusedItem",
18
+ },
16
19
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: () => {
17
20
  throw new Error("No data-loader registered");
18
21
  }, retrieveChildrenIds: () => {
@@ -20,7 +23,6 @@ export const treeFeature = {
20
23
  }, isItemExpanded: (itemId) => instance.getState().expandedItems.includes(itemId), getItemsMeta: () => {
21
24
  const { rootItemId } = instance.getConfig();
22
25
  const { expandedItems } = instance.getState();
23
- // console.log("!", instance.getConfig());
24
26
  const flatItems = [];
25
27
  const recursiveAdd = (itemId, parentId, level, setSize, posInSet) => {
26
28
  var _a;
@@ -47,23 +49,23 @@ export const treeFeature = {
47
49
  }
48
50
  return flatItems;
49
51
  }, expandItem: (itemId) => {
50
- var _a, _b, _c;
52
+ var _a;
51
53
  if (!instance.getItemInstance(itemId).isFolder()) {
52
54
  return;
53
55
  }
54
56
  if ((_a = instance.getState().loadingItems) === null || _a === void 0 ? void 0 : _a.includes(itemId)) {
55
57
  return;
56
58
  }
57
- (_c = (_b = instance
58
- .getConfig()).setExpandedItems) === null || _c === void 0 ? void 0 : _c.call(_b, (expandedItems) => [...expandedItems, itemId]);
59
+ instance.applySubStateUpdate("expandedItems", (expandedItems) => [
60
+ ...expandedItems,
61
+ itemId,
62
+ ]);
59
63
  instance.rebuildTree();
60
64
  }, collapseItem: (itemId) => {
61
- var _a, _b;
62
65
  if (!instance.getItemInstance(itemId).isFolder()) {
63
66
  return;
64
67
  }
65
- (_b = (_a = instance
66
- .getConfig()).setExpandedItems) === null || _b === void 0 ? void 0 : _b.call(_a, (expandedItems) => expandedItems.filter((id) => id !== itemId));
68
+ instance.applySubStateUpdate("expandedItems", (expandedItems) => expandedItems.filter((id) => id !== itemId));
67
69
  instance.rebuildTree();
68
70
  },
69
71
  // TODO memo
@@ -71,8 +73,7 @@ export const treeFeature = {
71
73
  var _a, _b;
72
74
  return ((_b = instance.getItemInstance((_a = instance.getState().focusedItem) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : instance.getItems()[0]);
73
75
  }, focusItem: (itemId) => {
74
- var _a, _b;
75
- (_b = (_a = instance.getConfig()).setFocusedItem) === null || _b === void 0 ? void 0 : _b.call(_a, itemId);
76
+ instance.applySubStateUpdate("focusedItem", itemId);
76
77
  }, focusNextItem: () => {
77
78
  const { index } = instance.getFocusedItem().getItemMeta();
78
79
  const nextIndex = Math.min(index + 1, instance.getItems().length - 1);
@@ -81,7 +82,7 @@ export const treeFeature = {
81
82
  const { index } = instance.getFocusedItem().getItemMeta();
82
83
  const nextIndex = Math.max(index - 1, 0);
83
84
  instance.focusItem(instance.getItems()[nextIndex].getId());
84
- }, updateDomFocus: (scrollIntoView) => {
85
+ }, updateDomFocus: () => {
85
86
  // Required because if the state is managed outside in react, the state only updated during next render
86
87
  setTimeout(() => __awaiter(void 0, void 0, void 0, function* () {
87
88
  var _a, _b;
@@ -92,9 +93,6 @@ export const treeFeature = {
92
93
  if (!focusedElement)
93
94
  return;
94
95
  focusedElement.focus();
95
- // if (scrollIntoView) {
96
- // focusedElement.scrollIntoView();
97
- // }
98
96
  }));
99
97
  }, getContainerProps: () => {
100
98
  var _a;
@@ -102,11 +100,15 @@ export const treeFeature = {
102
100
  } })),
103
101
  createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { isLoading: () => {
104
102
  throw new Error("No data-loader registered");
105
- }, getId: () => item.getItemMeta().itemId, getProps: () => {
103
+ }, scrollTo: (scrollIntoViewArg) => __awaiter(void 0, void 0, void 0, function* () {
104
+ var _a, _b;
105
+ (_b = (_a = tree.getConfig()).scrollToItem) === null || _b === void 0 ? void 0 : _b.call(_a, item);
106
+ yield poll(() => item.getElement() !== null, 20);
107
+ item.getElement().scrollIntoView(scrollIntoViewArg);
108
+ }), getId: () => item.getItemMeta().itemId, getProps: () => {
106
109
  var _a;
107
110
  const itemMeta = item.getItemMeta();
108
- return Object.assign(Object.assign({}, (_a = prev.getProps) === null || _a === void 0 ? void 0 : _a.call(prev)), { role: "treeitem", "aria-setsize": itemMeta.setSize, "aria-posinset": itemMeta.posInSet, "aria-selected": false, "aria-label": item.getItemName(), "aria-level": itemMeta.level, tabIndex: item.isFocused() ? 0 : -1, onClick: item.getMemoizedProp("tree/onClick", () => (e) => {
109
- console.log("onClick", item.getId());
111
+ return Object.assign(Object.assign({}, (_a = prev.getProps) === null || _a === void 0 ? void 0 : _a.call(prev)), { role: "treeitem", "aria-setsize": itemMeta.setSize, "aria-posinset": itemMeta.posInSet, "aria-selected": "false", "aria-label": item.getItemName(), "aria-level": itemMeta.level, tabIndex: item.isFocused() ? 0 : -1, onClick: item.getMemoizedProp("tree/onClick", () => (e) => {
110
112
  item.setFocused();
111
113
  item.primaryAction();
112
114
  if (e.ctrlKey || e.shiftKey || e.metaKey) {
@@ -135,12 +137,9 @@ export const treeFeature = {
135
137
  }
136
138
  }
137
139
  return tree.getItemInstance(tree.getConfig().rootItemId);
138
- }, () => [item.getItemMeta()]), getIndexInParent: () => {
139
- var _a, _b;
140
- return item.getItemMeta().index -
141
- ((_b = (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.getItemMeta().index) !== null && _b !== void 0 ? _b : 0) -
142
- 1;
143
- }, getChildren: () => tree
140
+ }, () => [item.getItemMeta()]),
141
+ // TODO remove
142
+ getIndexInParent: () => item.getItemMeta().posInSet, getChildren: () => tree
144
143
  .retrieveChildrenIds(item.getItemMeta().itemId)
145
144
  .map((id) => tree.getItemInstance(id)), getTree: () => tree, getItemAbove: () => tree.getItems()[item.getItemMeta().index - 1], getItemBelow: () => tree.getItems()[item.getItemMeta().index + 1], getMemoizedProp: (name, create, deps) => {
146
145
  var _a, _b, _c, _d, _e;
@@ -203,7 +202,7 @@ export const treeFeature = {
203
202
  if ((!item.isExpanded() || !item.isFolder()) &&
204
203
  item.getItemMeta().level !== 0) {
205
204
  (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.setFocused();
206
- tree.updateDomFocus(true);
205
+ tree.updateDomFocus();
207
206
  }
208
207
  else {
209
208
  item.collapse();
@@ -34,8 +34,7 @@ export type TreeFeatureDef<T> = {
34
34
  getFocusedItem: () => ItemInstance<any>;
35
35
  focusNextItem: () => void;
36
36
  focusPreviousItem: () => void;
37
- scrollToItem: (item: ItemInstance<any>) => void;
38
- updateDomFocus: (scrollIntoView?: boolean) => void;
37
+ updateDomFocus: () => void;
39
38
  getContainerProps: () => Record<string, any>;
40
39
  };
41
40
  itemInstance: {
@@ -57,6 +56,7 @@ export type TreeFeatureDef<T> = {
57
56
  getItemAbove: () => ItemInstance<T> | null;
58
57
  getItemBelow: () => ItemInstance<T> | null;
59
58
  getMemoizedProp: <X>(name: string, create: () => X, deps?: any[]) => X;
59
+ scrollTo: (scrollIntoViewArg?: boolean | ScrollIntoViewOptions) => Promise<void>;
60
60
  };
61
61
  hotkeys: "focusNextItem" | "focusPreviousItem" | "expandOrDown" | "collapseOrUp" | "focusFirstItem" | "focusLastItem";
62
62
  };
@@ -18,4 +18,6 @@ export * from "./features/drag-and-drop/feature";
18
18
  export * from "./features/search/feature";
19
19
  export * from "./features/renaming/feature";
20
20
  export * from "./features/expand-all/feature";
21
- export * from "./data-adapters/nested-data-adapter";
21
+ export * from "./utilities/create-on-drop-handler";
22
+ export * from "./utilities/insert-items-at-target";
23
+ export * from "./utilities/remove-items-from-parents";
package/lib/esm/index.js CHANGED
@@ -18,4 +18,6 @@ export * from "./features/drag-and-drop/feature";
18
18
  export * from "./features/search/feature";
19
19
  export * from "./features/renaming/feature";
20
20
  export * from "./features/expand-all/feature";
21
- export * from "./data-adapters/nested-data-adapter";
21
+ export * from "./utilities/create-on-drop-handler";
22
+ export * from "./utilities/insert-items-at-target";
23
+ export * from "./utilities/remove-items-from-parents";
@@ -51,7 +51,9 @@ export type HotkeysConfig<T, F extends FeatureDef = FeatureDefs<T>> = Record<Hot
51
51
  export type CustomHotkeysConfig<T, F extends FeatureDef = FeatureDefs<T>> = Partial<Record<HotkeyName<F> | `custom${string}`, Partial<HotkeyConfig<T>>>>;
52
52
  export type FeatureImplementation<T = any, D extends FeatureDef = any, F extends FeatureDef = EmptyFeatureDef> = {
53
53
  key?: string;
54
- dependingFeatures?: string[];
54
+ deps?: string[];
55
+ overwrites?: string[];
56
+ stateHandlerNames?: Partial<Record<keyof MergedFeatures<F>["state"], keyof MergedFeatures<F>["config"]>>;
55
57
  getInitialState?: (initialState: Partial<MergedFeatures<F>["state"]>, tree: MergedFeatures<F>["treeInstance"]) => Partial<D["state"] & MergedFeatures<F>["state"]>;
56
58
  getDefaultConfig?: (defaultConfig: Partial<MergedFeatures<F>["config"]>, tree: MergedFeatures<F>["treeInstance"]) => Partial<D["config"] & MergedFeatures<F>["config"]>;
57
59
  createTreeInstance?: (prev: MergedFeatures<F>["treeInstance"], instance: MergedFeatures<F>["treeInstance"]) => D["treeInstance"] & MergedFeatures<F>["treeInstance"];
@@ -60,9 +62,6 @@ export type FeatureImplementation<T = any, D extends FeatureDef = any, F extends
60
62
  onTreeUnmount?: (instance: MergedFeatures<F>["treeInstance"], treeElement: HTMLElement) => void;
61
63
  onItemMount?: (instance: MergedFeatures<F>["itemInstance"], itemElement: HTMLElement, tree: MergedFeatures<F>["treeInstance"]) => void;
62
64
  onItemUnmount?: (instance: MergedFeatures<F>["itemInstance"], itemElement: HTMLElement, tree: MergedFeatures<F>["treeInstance"]) => void;
63
- setState?: (instance: MergedFeatures<F>["treeInstance"]) => void;
64
- onConfigChange?: (instance: MergedFeatures<F>["treeInstance"]) => void;
65
- onStateOrConfigChange?: (instance: MergedFeatures<F>["treeInstance"]) => void;
66
65
  hotkeys?: HotkeysConfig<T, D>;
67
66
  };
68
67
  export {};
@@ -0,0 +1,3 @@
1
+ import { ItemInstance } from "../types/core";
2
+ import { DropTarget } from "../features/drag-and-drop/types";
3
+ export declare const createOnDropHandler: <T>(onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void) => (items: ItemInstance<T>[], target: DropTarget<T>) => void;
@@ -0,0 +1,7 @@
1
+ import { removeItemsFromParents } from "./remove-items-from-parents";
2
+ import { insertItemsAtTarget } from "./insert-items-at-target";
3
+ export const createOnDropHandler = (onChangeChildren) => (items, target) => {
4
+ const itemIds = items.map((item) => item.getId());
5
+ removeItemsFromParents(items, onChangeChildren);
6
+ insertItemsAtTarget(itemIds, target, onChangeChildren);
7
+ };
@@ -0,0 +1,3 @@
1
+ import { ItemInstance } from "../types/core";
2
+ import { DropTarget } from "../features/drag-and-drop/types";
3
+ export declare const insertItemsAtTarget: <T>(itemIds: string[], target: DropTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void) => void;
@@ -0,0 +1,20 @@
1
+ export const insertItemsAtTarget = (itemIds, target, onChangeChildren) => {
2
+ // add moved items to new common parent, if dropped onto parent
3
+ if (target.childIndex === null) {
4
+ onChangeChildren(target.item, [
5
+ ...target.item.getChildren().map((item) => item.getId()),
6
+ ...itemIds,
7
+ ]);
8
+ // TODO items[0].getTree().rebuildTree();
9
+ return;
10
+ }
11
+ // add moved items to new common parent, if dropped between siblings
12
+ const oldChildren = target.item.getChildren();
13
+ const newChildren = [
14
+ ...oldChildren.slice(0, target.insertionIndex).map((item) => item.getId()),
15
+ ...itemIds,
16
+ ...oldChildren.slice(target.insertionIndex).map((item) => item.getId()),
17
+ ];
18
+ onChangeChildren(target.item, newChildren);
19
+ target.item.getTree().rebuildTree();
20
+ };
@@ -0,0 +1,2 @@
1
+ import { ItemInstance } from "../types/core";
2
+ export declare const removeItemsFromParents: <T>(movedItems: ItemInstance<T>[], onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void) => void;
@@ -0,0 +1,13 @@
1
+ export const removeItemsFromParents = (movedItems, onChangeChildren) => {
2
+ var _a;
3
+ // TODO bulk sibling changes together
4
+ for (const item of movedItems) {
5
+ const siblings = (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.getChildren();
6
+ if (siblings) {
7
+ onChangeChildren(item.getParent(), siblings
8
+ .filter((sibling) => sibling.getId() !== item.getId())
9
+ .map((i) => i.getId()));
10
+ }
11
+ }
12
+ movedItems[0].getTree().rebuildTree();
13
+ };
@@ -1,9 +1,6 @@
1
- import { ItemInstance, TreeState, Updater } from "./types/core";
2
- import { DropTarget } from "./features/drag-and-drop/types";
1
+ import { TreeState, Updater } from "./types/core";
3
2
  export type NoInfer<T> = [T][T extends any ? 0 : never];
4
3
  export declare const memo: <D extends readonly any[], R>(fn: (...args_0: D) => R, deps: () => [...D]) => () => R;
5
4
  export declare function functionalUpdate<T>(updater: Updater<T>, input: T): T;
6
5
  export declare function makeStateUpdater<K extends keyof TreeState<any>>(key: K, instance: unknown): (updater: Updater<TreeState<any>[K]>) => void;
7
- export declare const scrollIntoView: (element: Element | undefined | null) => void;
8
- export declare const performItemsMove: <T>(items: ItemInstance<T>[], target: DropTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildren: ItemInstance<T>[]) => void) => void;
9
6
  export declare const poll: (fn: () => boolean, interval?: number, timeout?: number) => Promise<void>;
package/lib/esm/utils.js CHANGED
@@ -31,56 +31,6 @@ export function makeStateUpdater(key, instance) {
31
31
  });
32
32
  };
33
33
  }
34
- export const scrollIntoView = (element) => {
35
- if (!element) {
36
- return;
37
- }
38
- if (element.scrollIntoViewIfNeeded) {
39
- element.scrollIntoViewIfNeeded();
40
- }
41
- else {
42
- const boundingBox = element.getBoundingClientRect();
43
- const isElementInViewport = boundingBox.top >= 0 &&
44
- boundingBox.left >= 0 &&
45
- boundingBox.bottom <=
46
- (window.innerHeight || document.documentElement.clientHeight) &&
47
- boundingBox.right <=
48
- (window.innerWidth || document.documentElement.clientWidth);
49
- if (!isElementInViewport) {
50
- element.scrollIntoView();
51
- }
52
- }
53
- };
54
- export const performItemsMove = (items, target, onChangeChildren) => {
55
- var _a;
56
- const numberOfDragItemsBeforeTarget = !target.childIndex
57
- ? 0
58
- : target.item
59
- .getChildren()
60
- .slice(0, target.childIndex)
61
- .filter((child) => items.some((item) => item.getId() === child.getId()))
62
- .length;
63
- // TODO bulk sibling changes together
64
- for (const item of items) {
65
- const siblings = (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.getChildren();
66
- if (siblings) {
67
- onChangeChildren(item.getParent(), siblings.filter((sibling) => sibling.getId() !== item.getId()));
68
- }
69
- }
70
- if (target.childIndex === null) {
71
- onChangeChildren(target.item, [...target.item.getChildren(), ...items]);
72
- items[0].getTree().rebuildTree();
73
- return;
74
- }
75
- const oldChildren = target.item.getChildren();
76
- const newChildren = [
77
- ...oldChildren.slice(0, target.childIndex - numberOfDragItemsBeforeTarget),
78
- ...items,
79
- ...oldChildren.slice(target.childIndex - numberOfDragItemsBeforeTarget),
80
- ];
81
- onChangeChildren(target.item, newChildren);
82
- items[0].getTree().rebuildTree();
83
- };
84
34
  export const poll = (fn, interval = 100, timeout = 1000) => new Promise((resolve) => {
85
35
  let clear;
86
36
  const i = setInterval(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-tree/core",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "main": "lib/cjs/index.js",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/esm/index.d.ts",
@@ -30,12 +30,42 @@ const buildItemInstance = (
30
30
  return itemInstance;
31
31
  };
32
32
 
33
+ const verifyFeatures = (features: FeatureImplementation[] | undefined) => {
34
+ const loadedFeatures = features?.map((feature) => feature.key);
35
+ for (const feature of features ?? []) {
36
+ const missingDependency = feature.deps?.find(
37
+ (dep) => !loadedFeatures?.includes(dep)
38
+ );
39
+ if (missingDependency) {
40
+ throw new Error(`${feature.key} needs ${missingDependency}`);
41
+ }
42
+ }
43
+ };
44
+
45
+ const compareFeatures = (
46
+ feature1: FeatureImplementation,
47
+ feature2: FeatureImplementation
48
+ ) => {
49
+ if (feature2.key && feature1.overwrites?.includes(feature2.key)) {
50
+ return 1;
51
+ }
52
+ return -1;
53
+ };
54
+
55
+ const sortFeatures = (features: FeatureImplementation[] = []) =>
56
+ features.sort(compareFeatures);
57
+
33
58
  export const createTree = <T>(
34
59
  initialConfig: TreeConfig<T>
35
60
  ): TreeInstance<T> => {
36
61
  const treeInstance: TreeInstance<T> = {} as any;
37
62
 
38
- const additionalFeatures = [treeFeature, ...(initialConfig.features ?? [])];
63
+ const additionalFeatures = [
64
+ treeFeature,
65
+ ...sortFeatures(initialConfig.features),
66
+ ];
67
+ verifyFeatures(additionalFeatures);
68
+
39
69
  let state = additionalFeatures.reduce(
40
70
  (acc, feature) => feature.getInitialState?.(acc, treeInstance) ?? acc,
41
71
  initialConfig.initialState ?? initialConfig.state ?? {}
@@ -44,6 +74,10 @@ export const createTree = <T>(
44
74
  (acc, feature) => feature.getDefaultConfig?.(acc, treeInstance) ?? acc,
45
75
  initialConfig
46
76
  ) as TreeConfig<T>;
77
+ const stateHandlerNames = additionalFeatures.reduce(
78
+ (acc, feature) => ({ ...acc, ...feature.stateHandlerNames }),
79
+ {} as Record<string, string>
80
+ );
47
81
 
48
82
  let treeElement: HTMLElement | undefined | null;
49
83
  const treeDataRef: { current: any } = { current: {} };
@@ -109,10 +143,14 @@ export const createTree = <T>(
109
143
  ...prev,
110
144
  getState: () => state,
111
145
  setState: (updater) => {
112
- state = typeof updater === "function" ? updater(state) : updater;
146
+ // Not necessary, since I think the subupdate below keeps the state fresh anyways?
147
+ // state = typeof updater === "function" ? updater(state) : updater;
113
148
  config.setState?.(state);
114
- eachFeature((feature) => feature.setState?.(treeInstance));
115
- eachFeature((feature) => feature.onStateOrConfigChange?.(treeInstance));
149
+ },
150
+ applySubStateUpdate: (stateName, updater) => {
151
+ state[stateName] =
152
+ typeof updater === "function" ? updater(state[stateName]) : updater;
153
+ config[stateHandlerNames[stateName]]!(state[stateName]);
116
154
  },
117
155
  rebuildTree: () => {
118
156
  rebuildItemMeta(mainFeature);
@@ -124,11 +162,7 @@ export const createTree = <T>(
124
162
 
125
163
  if (config.state) {
126
164
  state = { ...state, ...config.state };
127
- eachFeature((feature) => feature.setState?.(treeInstance));
128
165
  }
129
-
130
- eachFeature((feature) => feature.onConfigChange?.(treeInstance));
131
- eachFeature((feature) => feature.onStateOrConfigChange?.(treeInstance));
132
166
  },
133
167
  getItemInstance: (itemId) => itemInstancesMap[itemId],
134
168
  getItems: () => itemInstances,
@@ -178,7 +212,6 @@ export const createTree = <T>(
178
212
  }),
179
213
  };
180
214
 
181
- // todo sort features
182
215
  const features = [mainFeature, ...additionalFeatures];
183
216
 
184
217
  for (const feature of features) {
@@ -10,7 +10,6 @@ export const asyncDataLoaderFeature: FeatureImplementation<
10
10
  MainFeatureDef | TreeFeatureDef<any> | AsyncDataLoaderFeatureDef<any>
11
11
  > = {
12
12
  key: "async-data-loader",
13
- dependingFeatures: ["main"],
14
13
 
15
14
  getInitialState: (initialState) => ({
16
15
  loadingItems: [],
@@ -22,6 +21,10 @@ export const asyncDataLoaderFeature: FeatureImplementation<
22
21
  ...defaultConfig,
23
22
  }),
24
23
 
24
+ stateHandlerNames: {
25
+ loadingItems: "setLoadingItems",
26
+ },
27
+
25
28
  createTreeInstance: (prev, instance) => ({
26
29
  ...prev,
27
30
 
@@ -36,11 +39,14 @@ export const asyncDataLoaderFeature: FeatureImplementation<
36
39
  }
37
40
 
38
41
  if (!instance.getState().loadingItems.includes(itemId)) {
39
- config.setLoadingItems?.((loadingItems) => [...loadingItems, itemId]);
42
+ instance.applySubStateUpdate("loadingItems", (loadingItems) => [
43
+ ...loadingItems,
44
+ itemId,
45
+ ]);
40
46
  config.asyncDataLoader?.getItem(itemId).then((item) => {
41
47
  dataRef.current.itemData[itemId] = item;
42
48
  config.onLoadedItem?.(itemId, item);
43
- config.setLoadingItems?.((loadingItems) =>
49
+ instance.applySubStateUpdate("loadingItems", (loadingItems) =>
44
50
  loadingItems.filter((id) => id !== itemId)
45
51
  );
46
52
  });
@@ -62,7 +68,10 @@ export const asyncDataLoaderFeature: FeatureImplementation<
62
68
  return [];
63
69
  }
64
70
 
65
- config.setLoadingItems?.((loadingItems) => [...loadingItems, itemId]);
71
+ instance.applySubStateUpdate("loadingItems", (loadingItems) => [
72
+ ...loadingItems,
73
+ itemId,
74
+ ]);
66
75
 
67
76
  if (config.asyncDataLoader?.getChildrenWithData) {
68
77
  config.asyncDataLoader?.getChildrenWithData(itemId).then((children) => {
@@ -73,7 +82,7 @@ export const asyncDataLoaderFeature: FeatureImplementation<
73
82
  const childrenIds = children.map(({ id }) => id);
74
83
  dataRef.current.childrenIds[itemId] = childrenIds;
75
84
  config.onLoadedChildren?.(itemId, childrenIds);
76
- config.setLoadingItems?.((loadingItems) =>
85
+ instance.applySubStateUpdate("loadingItems", (loadingItems) =>
77
86
  loadingItems.filter((id) => id !== itemId)
78
87
  );
79
88
  instance.rebuildTree();
@@ -82,7 +91,7 @@ export const asyncDataLoaderFeature: FeatureImplementation<
82
91
  config.asyncDataLoader?.getChildren(itemId).then((childrenIds) => {
83
92
  dataRef.current.childrenIds[itemId] = childrenIds;
84
93
  config.onLoadedChildren?.(itemId, childrenIds);
85
- config.setLoadingItems?.((loadingItems) =>
94
+ instance.applySubStateUpdate("loadingItems", (loadingItems) =>
86
95
  loadingItems.filter((id) => id !== itemId)
87
96
  );
88
97
  instance.rebuildTree();
@@ -1,5 +1,5 @@
1
1
  import { FeatureDefs, FeatureImplementation } from "../../types/core";
2
- import { DndDataRef, DragAndDropFeatureDef } from "./types";
2
+ import { DndDataRef, DragAndDropFeatureDef, DragLineData } from "./types";
3
3
  import { canDrop, getDragCode, getDropTarget } from "./utils";
4
4
  import { makeStateUpdater } from "../../utils";
5
5
 
@@ -9,20 +9,64 @@ export const dragAndDropFeature: FeatureImplementation<
9
9
  FeatureDefs<any>
10
10
  > = {
11
11
  key: "dragAndDrop",
12
- dependingFeatures: ["main", "tree", "selection"],
12
+ deps: ["selection"],
13
13
 
14
14
  getDefaultConfig: (defaultConfig, tree) => ({
15
15
  canDrop: (_, target) => target.item.isFolder(),
16
+ canDropForeignDragObject: () => false,
16
17
  setDndState: makeStateUpdater("dnd", tree),
17
18
  ...defaultConfig,
18
19
  }),
19
20
 
21
+ stateHandlerNames: {
22
+ dnd: "setDndState",
23
+ },
24
+
20
25
  createTreeInstance: (prev, tree) => ({
21
26
  ...prev,
22
27
 
23
28
  getDropTarget: () => {
24
29
  return tree.getState().dnd?.dragTarget ?? null;
25
30
  },
31
+
32
+ getDragLineData: (): DragLineData | null => {
33
+ const target = tree.getDropTarget();
34
+ const intend = (target?.item.getItemMeta().level ?? 0) + 1;
35
+
36
+ if (!target || target.childIndex === null) return null;
37
+
38
+ const children = target.item.getChildren();
39
+
40
+ if (target.childIndex === children.length) {
41
+ const bb = children[target.childIndex - 1]
42
+ ?.getElement()
43
+ ?.getBoundingClientRect();
44
+
45
+ if (bb) {
46
+ return {
47
+ intend,
48
+ top: bb.bottom,
49
+ left: bb.left,
50
+ right: bb.right,
51
+ };
52
+ }
53
+ }
54
+
55
+ const bb = children[target.childIndex]
56
+ ?.getElement()
57
+ ?.getBoundingClientRect();
58
+
59
+ if (bb) {
60
+ return {
61
+ intend,
62
+ top: bb.top,
63
+ left: bb.left,
64
+ right: bb.right,
65
+ };
66
+ }
67
+
68
+ return null;
69
+ },
26
70
  }),
27
71
 
28
72
  createItemInstance: (prev, item, tree) => ({
@@ -52,7 +96,7 @@ export const dragAndDropFeature: FeatureImplementation<
52
96
  e.dataTransfer?.setData(format, data);
53
97
  }
54
98
 
55
- tree.getConfig().setDndState?.({
99
+ tree.applySubStateUpdate("dnd", {
56
100
  draggedItems: items,
57
101
  draggingOverItem: tree.getFocusedItem(),
58
102
  });
@@ -82,7 +126,7 @@ export const dragAndDropFeature: FeatureImplementation<
82
126
 
83
127
  dataRef.current.lastDragCode = nextDragCode;
84
128
 
85
- tree.getConfig().setDndState?.((state) => ({
129
+ tree.applySubStateUpdate("dnd", (state) => ({
86
130
  ...state,
87
131
  dragTarget: target,
88
132
  draggingOverItem: item,
@@ -92,13 +136,24 @@ export const dragAndDropFeature: FeatureImplementation<
92
136
  onDragLeave: item.getMemoizedProp("dnd/onDragLeave", () => () => {
93
137
  const dataRef = tree.getDataRef<DndDataRef>();
94
138
  dataRef.current.lastDragCode = "no-drag";
95
- tree.getConfig().setDndState?.((state) => ({
139
+ tree.applySubStateUpdate("dnd", (state) => ({
96
140
  ...state,
97
141
  draggingOverItem: undefined,
98
142
  dragTarget: undefined,
99
143
  }));
100
144
  }),
101
145
 
146
+ onDragEnd: item.getMemoizedProp("dnd/onDragEnd", () => (e) => {
147
+ const draggedItems = tree.getState().dnd?.draggedItems;
148
+ tree.applySubStateUpdate("dnd", null);
149
+
150
+ if (e.dataTransfer.dropEffect === "none" || !draggedItems) {
151
+ return;
152
+ }
153
+
154
+ tree.getConfig().onCompleteForeignDrop?.(draggedItems);
155
+ }),
156
+
102
157
  onDrop: item.getMemoizedProp("dnd/onDrop", () => (e) => {
103
158
  const dataRef = tree.getDataRef<DndDataRef>();
104
159
  const target = getDropTarget(e, item, tree);
@@ -112,7 +167,7 @@ export const dragAndDropFeature: FeatureImplementation<
112
167
  const draggedItems = tree.getState().dnd?.draggedItems;
113
168
 
114
169
  dataRef.current.lastDragCode = undefined;
115
- tree.getConfig().setDndState?.(null);
170
+ tree.applySubStateUpdate("dnd", null);
116
171
 
117
172
  if (draggedItems) {
118
173
  config.onDrop?.(draggedItems, target);
@@ -131,19 +186,25 @@ export const dragAndDropFeature: FeatureImplementation<
131
186
  isDropTargetAbove: () => {
132
187
  const target = tree.getDropTarget();
133
188
 
134
- if (!target || target.childIndex === null) return false;
135
- const targetIndex = target.item.getItemMeta().index;
136
-
137
- return targetIndex + target.childIndex + 1 === item.getItemMeta().index;
189
+ if (
190
+ !target ||
191
+ target.childIndex === null ||
192
+ target.item !== item.getParent()
193
+ )
194
+ return false;
195
+ return target.childIndex === item.getItemMeta().posInSet;
138
196
  },
139
197
 
140
198
  isDropTargetBelow: () => {
141
199
  const target = tree.getDropTarget();
142
200
 
143
- if (!target || target.childIndex === null) return false;
144
- const targetIndex = target.item.getItemMeta().index;
145
-
146
- return targetIndex + target.childIndex === item.getItemMeta().index;
201
+ if (
202
+ !target ||
203
+ target.childIndex === null ||
204
+ target.item !== item.getParent()
205
+ )
206
+ return false;
207
+ return target.childIndex - 1 === item.getItemMeta().posInSet;
147
208
  },
148
209
 
149
210
  isDraggingOver: () => {