@headless-tree/core 0.0.5 → 0.0.6

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 (90) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/cjs/core/create-tree.js +7 -6
  3. package/lib/cjs/features/async-data-loader/feature.js +30 -21
  4. package/lib/cjs/features/drag-and-drop/feature.js +29 -17
  5. package/lib/cjs/features/drag-and-drop/types.d.ts +14 -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 -9
  9. package/lib/cjs/features/expand-all/types.d.ts +2 -2
  10. package/lib/cjs/features/main/types.d.ts +3 -1
  11. package/lib/cjs/features/renaming/feature.js +10 -9
  12. package/lib/cjs/features/search/feature.js +21 -7
  13. package/lib/cjs/features/search/types.d.ts +1 -1
  14. package/lib/cjs/features/selection/feature.js +5 -3
  15. package/lib/cjs/features/sync-data-loader/feature.js +6 -0
  16. package/lib/cjs/features/sync-data-loader/types.d.ts +1 -1
  17. package/lib/cjs/features/tree/feature.js +23 -23
  18. package/lib/cjs/features/tree/types.d.ts +2 -2
  19. package/lib/cjs/index.d.ts +3 -1
  20. package/lib/cjs/index.js +3 -1
  21. package/lib/cjs/types/core.d.ts +1 -3
  22. package/lib/cjs/utilities/create-on-drop-handler.d.ts +3 -0
  23. package/lib/cjs/utilities/create-on-drop-handler.js +11 -0
  24. package/lib/cjs/utilities/insert-items-at-target.d.ts +3 -0
  25. package/lib/cjs/utilities/insert-items-at-target.js +24 -0
  26. package/lib/cjs/utilities/remove-items-from-parents.d.ts +2 -0
  27. package/lib/cjs/utilities/remove-items-from-parents.js +17 -0
  28. package/lib/cjs/utils.d.ts +1 -4
  29. package/lib/cjs/utils.js +1 -53
  30. package/lib/esm/core/create-tree.js +7 -6
  31. package/lib/esm/features/async-data-loader/feature.js +30 -21
  32. package/lib/esm/features/drag-and-drop/feature.js +29 -17
  33. package/lib/esm/features/drag-and-drop/types.d.ts +14 -1
  34. package/lib/esm/features/drag-and-drop/utils.d.ts +1 -1
  35. package/lib/esm/features/drag-and-drop/utils.js +35 -18
  36. package/lib/esm/features/expand-all/feature.js +12 -9
  37. package/lib/esm/features/expand-all/types.d.ts +2 -2
  38. package/lib/esm/features/main/types.d.ts +3 -1
  39. package/lib/esm/features/renaming/feature.js +10 -9
  40. package/lib/esm/features/search/feature.js +21 -7
  41. package/lib/esm/features/search/types.d.ts +1 -1
  42. package/lib/esm/features/selection/feature.js +5 -3
  43. package/lib/esm/features/sync-data-loader/feature.js +6 -0
  44. package/lib/esm/features/sync-data-loader/types.d.ts +1 -1
  45. package/lib/esm/features/tree/feature.js +23 -23
  46. package/lib/esm/features/tree/types.d.ts +2 -2
  47. package/lib/esm/index.d.ts +3 -1
  48. package/lib/esm/index.js +3 -1
  49. package/lib/esm/types/core.d.ts +1 -3
  50. package/lib/esm/utilities/create-on-drop-handler.d.ts +3 -0
  51. package/lib/esm/utilities/create-on-drop-handler.js +7 -0
  52. package/lib/esm/utilities/insert-items-at-target.d.ts +3 -0
  53. package/lib/esm/utilities/insert-items-at-target.js +20 -0
  54. package/lib/esm/utilities/remove-items-from-parents.d.ts +2 -0
  55. package/lib/esm/utilities/remove-items-from-parents.js +13 -0
  56. package/lib/esm/utils.d.ts +1 -4
  57. package/lib/esm/utils.js +0 -50
  58. package/package.json +1 -1
  59. package/src/core/create-tree.ts +11 -7
  60. package/src/features/async-data-loader/feature.ts +15 -5
  61. package/src/features/drag-and-drop/feature.ts +34 -12
  62. package/src/features/drag-and-drop/types.ts +23 -6
  63. package/src/features/drag-and-drop/utils.ts +53 -24
  64. package/src/features/expand-all/feature.ts +10 -8
  65. package/src/features/expand-all/types.ts +2 -2
  66. package/src/features/main/types.ts +6 -0
  67. package/src/features/renaming/feature.ts +10 -5
  68. package/src/features/search/feature.ts +22 -5
  69. package/src/features/search/types.ts +1 -0
  70. package/src/features/selection/feature.ts +6 -1
  71. package/src/features/sync-data-loader/feature.ts +17 -2
  72. package/src/features/sync-data-loader/types.ts +1 -1
  73. package/src/features/tree/feature.ts +23 -21
  74. package/src/features/tree/types.ts +4 -2
  75. package/src/index.ts +4 -1
  76. package/src/types/core.ts +4 -4
  77. package/src/utilities/create-on-drop-handler.ts +14 -0
  78. package/src/utilities/insert-items-at-target.ts +30 -0
  79. package/src/utilities/remove-items-from-parents.ts +21 -0
  80. package/src/utils.ts +1 -69
  81. package/lib/cjs/data-adapters/nested-data-adapter.d.ts +0 -9
  82. package/lib/cjs/data-adapters/nested-data-adapter.js +0 -32
  83. package/lib/cjs/data-adapters/types.d.ts +0 -7
  84. package/lib/cjs/data-adapters/types.js +0 -2
  85. package/lib/esm/data-adapters/nested-data-adapter.d.ts +0 -9
  86. package/lib/esm/data-adapters/nested-data-adapter.js +0 -28
  87. package/lib/esm/data-adapters/types.d.ts +0 -7
  88. package/lib/esm/data-adapters/types.js +0 -1
  89. package/src/data-adapters/nested-data-adapter.ts +0 -48
  90. package/src/data-adapters/types.ts +0 -9
@@ -1,4 +1,4 @@
1
- import { DropTargetPosition } from "./types";
1
+ import { DropTargetPosition, } from "./types";
2
2
  export const getDragCode = ({ item, childIndex }) => `${item.getId()}__${childIndex !== null && childIndex !== void 0 ? childIndex : "none"}`;
3
3
  export const getDropOffset = (e, item) => {
4
4
  var _a;
@@ -28,28 +28,45 @@ const getDropTargetPosition = (offset, topLinePercentage, bottomLinePercentage)
28
28
  }
29
29
  return DropTargetPosition.Item;
30
30
  };
31
- export const getDropTarget = (e, item, tree) => {
32
- var _a, _b;
31
+ export const getDropTarget = (e, item, tree, canDropInbetween = tree.getConfig().canDropInbetween) => {
32
+ var _a, _b, _c, _d;
33
33
  const config = tree.getConfig();
34
- const offset = getDropOffset(e, item);
35
- const dropOnItemTarget = { item, childIndex: null };
36
- const pos = getDropTargetPosition(offset, (_a = config.topLinePercentage) !== null && _a !== void 0 ? _a : 0.3, (_b = config.bottomLinePercentage) !== null && _b !== void 0 ? _b : 0.7);
37
- const inbetweenPos = getDropTargetPosition(offset, 0.5, 0.5);
38
- if (!config.canDropInbetween) {
39
- return dropOnItemTarget;
40
- }
41
- if (!canDrop(e.dataTransfer, dropOnItemTarget, tree)) {
42
- return {
43
- item: item.getParent(),
44
- childIndex: item.getIndexInParent() +
45
- (inbetweenPos === DropTargetPosition.Top ? 0 : 1),
46
- };
34
+ 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(),
38
+ childIndex: null,
39
+ insertionIndex: null,
40
+ };
41
+ if (!canDropInbetween) {
42
+ if (!canDrop(e.dataTransfer, parentTarget, tree)) {
43
+ return getDropTarget(e, item.getParent(), tree, false);
44
+ }
45
+ return itemTarget;
47
46
  }
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);
48
52
  if (pos === DropTargetPosition.Item) {
49
- return dropOnItemTarget;
53
+ return itemTarget;
54
+ }
55
+ if (!canDrop(e.dataTransfer, parentTarget, tree)) {
56
+ return getDropTarget(e, item.getParent(), tree, false);
50
57
  }
58
+ const childIndex = item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1);
59
+ const numberOfDragItemsBeforeTarget = item
60
+ .getParent()
61
+ .getChildren()
62
+ .slice(0, childIndex)
63
+ .reduce((counter, child) => child && (draggedItems === null || draggedItems === void 0 ? void 0 : draggedItems.some((i) => i.getId() === child.getId()))
64
+ ? ++counter
65
+ : counter, 0);
51
66
  return {
52
67
  item: item.getParent(),
53
- childIndex: item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1),
68
+ childIndex,
69
+ // TODO performance could be improved by computing this only when dragcode changed
70
+ insertionIndex: childIndex - numberOfDragItemsBeforeTarget,
54
71
  };
55
72
  };
@@ -13,24 +13,27 @@ export const expandAllFeature = {
13
13
  dependingFeatures: ["main", "tree"],
14
14
  createTreeInstance: (prev, tree) => (Object.assign(Object.assign({}, prev), { expandAll: (cancelToken) => __awaiter(void 0, void 0, void 0, function* () {
15
15
  yield Promise.all(tree.getItems().map((item) => item.expandAll(cancelToken)));
16
- }), collapseAll: () => __awaiter(void 0, void 0, void 0, function* () {
17
- var _a, _b;
18
- (_b = (_a = tree.getConfig()).setExpandedItems) === null || _b === void 0 ? void 0 : _b.call(_a, []);
19
- }) })),
16
+ }), collapseAll: () => {
17
+ tree.applySubStateUpdate("expandedItems", []);
18
+ tree.rebuildTree();
19
+ } })),
20
20
  createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { expandAll: (cancelToken) => __awaiter(void 0, void 0, void 0, function* () {
21
21
  if (cancelToken === null || cancelToken === void 0 ? void 0 : cancelToken.current) {
22
22
  return;
23
23
  }
24
+ if (!item.isFolder()) {
25
+ return;
26
+ }
24
27
  item.expand();
25
28
  yield poll(() => !tree.getState().loadingItems.includes(item.getId()));
26
29
  yield Promise.all(item.getChildren().map((child) => __awaiter(void 0, void 0, void 0, function* () {
27
30
  yield poll(() => !tree.getState().loadingItems.includes(child.getId()));
28
31
  yield (child === null || child === void 0 ? void 0 : child.expandAll(cancelToken));
29
32
  })));
30
- }), collapseAll: () => __awaiter(void 0, void 0, void 0, function* () {
31
- yield Promise.all(item.getChildren().map((child) => __awaiter(void 0, void 0, void 0, function* () {
32
- yield (child === null || child === void 0 ? void 0 : child.collapseAll());
33
- })));
33
+ }), collapseAll: () => {
34
+ for (const child of item.getChildren()) {
35
+ child === null || child === void 0 ? void 0 : child.collapseAll();
36
+ }
34
37
  item.collapse();
35
- }) })),
38
+ } })),
36
39
  };
@@ -5,13 +5,13 @@ export type ExpandAllFeatureDef = {
5
5
  expandAll: (cancelToken?: {
6
6
  current: boolean;
7
7
  }) => Promise<void>;
8
- collapseAll: () => Promise<void>;
8
+ collapseAll: () => void;
9
9
  };
10
10
  itemInstance: {
11
11
  expandAll: (cancelToken?: {
12
12
  current: boolean;
13
13
  }) => Promise<void>;
14
- collapseAll: () => Promise<void>;
14
+ collapseAll: () => void;
15
15
  };
16
16
  hotkeys: never;
17
17
  };
@@ -1,4 +1,4 @@
1
- import { FeatureImplementation, HotkeysConfig, ItemInstance, SetStateFn, TreeConfig, TreeState } from "../../types/core";
1
+ import { FeatureImplementation, HotkeysConfig, ItemInstance, SetStateFn, TreeConfig, TreeState, Updater } from "../../types/core";
2
2
  import { ItemMeta } from "../tree/types";
3
3
  export type MainFeatureDef<T = any> = {
4
4
  state: {};
@@ -9,6 +9,8 @@ export type MainFeatureDef<T = any> = {
9
9
  setState?: SetStateFn<TreeState<T>>;
10
10
  };
11
11
  treeInstance: {
12
+ /** @internal */
13
+ applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
12
14
  setState: SetStateFn<TreeState<T>>;
13
15
  getState: () => TreeState<T>;
14
16
  setConfig: SetStateFn<TreeConfig<T>>;
@@ -3,36 +3,37 @@ export const renamingFeature = {
3
3
  key: "renaming",
4
4
  dependingFeatures: ["main", "tree"],
5
5
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setRenamingItem: makeStateUpdater("renamingItem", tree), setRenamingValue: makeStateUpdater("renamingValue", tree), canRename: () => true }, defaultConfig)),
6
+ stateHandlerNames: {
7
+ renamingItem: "setRenamingItem",
8
+ renamingValue: "setRenamingValue",
9
+ },
6
10
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { startRenamingItem: (itemId) => {
7
- var _a, _b;
8
11
  const config = instance.getConfig();
9
12
  const item = instance.getItemInstance(itemId);
10
13
  if (!item.canRename()) {
11
14
  return;
12
15
  }
13
- (_a = config.setRenamingItem) === null || _a === void 0 ? void 0 : _a.call(config, itemId);
14
- (_b = config.setRenamingValue) === null || _b === void 0 ? void 0 : _b.call(config, item.getItemName());
16
+ instance.applySubStateUpdate("renamingItem", itemId);
17
+ instance.applySubStateUpdate("renamingValue", item.getItemName());
15
18
  }, getRenamingItem: () => {
16
19
  const itemId = instance.getState().renamingItem;
17
20
  return itemId ? instance.getItemInstance(itemId) : null;
18
21
  }, getRenamingValue: () => instance.getState().renamingValue || "", abortRenaming: () => {
19
- var _a, _b;
20
- (_b = (_a = instance.getConfig()).setRenamingItem) === null || _b === void 0 ? void 0 : _b.call(_a, null);
22
+ instance.applySubStateUpdate("renamingItem", null);
21
23
  }, completeRenaming: () => {
22
- var _a, _b, _c;
24
+ var _a;
23
25
  const config = instance.getConfig();
24
26
  const item = instance.getRenamingItem();
25
27
  if (item) {
26
28
  (_a = config.onRename) === null || _a === void 0 ? void 0 : _a.call(config, item, instance.getState().renamingValue || "");
27
29
  }
28
- (_c = (_b = instance.getConfig()).setRenamingItem) === null || _c === void 0 ? void 0 : _c.call(_b, null);
30
+ instance.applySubStateUpdate("renamingItem", null);
29
31
  }, isRenamingItem: () => !!instance.getState().renamingItem })),
30
32
  createItemInstance: (prev, instance, tree) => (Object.assign(Object.assign({}, prev), { getRenameInputProps: () => ({
31
33
  onBlur: () => tree.abortRenaming(),
32
34
  value: tree.getRenamingValue(),
33
35
  onChange: (e) => {
34
- var _a, _b;
35
- (_b = (_a = tree.getConfig()).setRenamingValue) === null || _b === void 0 ? void 0 : _b.call(_a, e.target.value);
36
+ tree.applySubStateUpdate("renamingValue", e.target.value);
36
37
  },
37
38
  }), canRename: () => { var _a, _b, _c; return (_c = (_b = (_a = tree.getConfig()).canRename) === null || _b === void 0 ? void 0 : _b.call(_a, instance)) !== null && _c !== void 0 ? _c : true; }, isRenaming: () => instance.getId() === tree.getState().renamingItem })),
38
39
  hotkeys: {
@@ -5,16 +5,19 @@ export const searchFeature = {
5
5
  getInitialState: (initialState) => (Object.assign({ search: null }, initialState)),
6
6
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setSearch: makeStateUpdater("search", tree), isSearchMatchingItem: (search, item) => search.length > 0 &&
7
7
  item.getItemName().toLowerCase().includes(search.toLowerCase()) }, defaultConfig)),
8
+ stateHandlerNames: {
9
+ search: "setSearch",
10
+ },
8
11
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { setSearch: (search) => {
9
- var _a, _b, _c;
10
- (_b = (_a = instance.getConfig()).setSearch) === null || _b === void 0 ? void 0 : _b.call(_a, search);
11
- (_c = instance
12
+ var _a;
13
+ instance.applySubStateUpdate("search", search);
14
+ (_a = instance
12
15
  .getItems()
13
16
  .find((item) => {
14
17
  var _a, _b;
15
18
  return (_b = (_a = instance
16
19
  .getConfig()).isSearchMatchingItem) === null || _b === void 0 ? void 0 : _b.call(_a, instance.getSearchValue(), item);
17
- })) === null || _c === void 0 ? void 0 : _c.setFocused();
20
+ })) === null || _a === void 0 ? void 0 : _a.setFocused();
18
21
  }, openSearch: (initialValue = "") => {
19
22
  instance.setSearch(initialValue);
20
23
  setTimeout(() => {
@@ -36,7 +39,7 @@ export const searchFeature = {
36
39
  value: instance.getSearchValue(),
37
40
  onChange: (e) => instance.setSearch(e.target.value),
38
41
  onBlur: () => instance.closeSearch(),
39
- }), getSearchMatchingItems: memo((search, items) => items.filter((item) => { var _a, _b; return (_b = (_a = instance.getConfig()).isSearchMatchingItem) === null || _b === void 0 ? void 0 : _b.call(_a, search, item); }), () => [instance.getSearchValue(), instance.getItems()]) })),
42
+ }), getSearchMatchingItems: memo((search, items) => items.filter((item) => { var _a, _b; return search && ((_b = (_a = instance.getConfig()).isSearchMatchingItem) === null || _b === void 0 ? void 0 : _b.call(_a, search, item)); }), () => [instance.getSearchValue(), instance.getItems()]) })),
40
43
  createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { isMatchingSearch: () => tree.getSearchMatchingItems().some((i) => i.getId() === item.getId()) })),
41
44
  hotkeys: {
42
45
  openSearch: {
@@ -57,30 +60,41 @@ export const searchFeature = {
57
60
  tree.closeSearch();
58
61
  },
59
62
  },
63
+ submitSearch: {
64
+ hotkey: "Enter",
65
+ allowWhenInputFocused: true,
66
+ isEnabled: (tree) => tree.isSearchOpen(),
67
+ handler: (e, tree) => {
68
+ tree.closeSearch();
69
+ tree.setSelectedItems([tree.getFocusedItem().getId()]);
70
+ },
71
+ },
60
72
  nextSearchItem: {
61
73
  hotkey: "ArrowDown",
62
74
  allowWhenInputFocused: true,
75
+ canRepeat: true,
63
76
  isEnabled: (tree) => tree.isSearchOpen(),
64
77
  handler: (e, tree) => {
65
- // TODO scroll into view
66
78
  const focusItem = tree
67
79
  .getSearchMatchingItems()
68
80
  .find((item) => item.getItemMeta().index >
69
81
  tree.getFocusedItem().getItemMeta().index);
70
82
  focusItem === null || focusItem === void 0 ? void 0 : focusItem.setFocused();
83
+ focusItem === null || focusItem === void 0 ? void 0 : focusItem.scrollTo({ block: "nearest", inline: "nearest" });
71
84
  },
72
85
  },
73
86
  previousSearchItem: {
74
87
  hotkey: "ArrowUp",
75
88
  allowWhenInputFocused: true,
89
+ canRepeat: true,
76
90
  isEnabled: (tree) => tree.isSearchOpen(),
77
91
  handler: (e, tree) => {
78
- // TODO scroll into view
79
92
  const focusItem = [...tree.getSearchMatchingItems()]
80
93
  .reverse()
81
94
  .find((item) => item.getItemMeta().index <
82
95
  tree.getFocusedItem().getItemMeta().index);
83
96
  focusItem === null || focusItem === void 0 ? void 0 : focusItem.setFocused();
97
+ focusItem === null || focusItem === void 0 ? void 0 : focusItem.scrollTo({ block: "nearest", inline: "nearest" });
84
98
  },
85
99
  },
86
100
  },
@@ -29,5 +29,5 @@ export type SearchFeatureDef<T> = {
29
29
  itemInstance: {
30
30
  isMatchingSearch: () => boolean;
31
31
  };
32
- hotkeys: "openSearch" | "closeSearch" | "nextSearchItem" | "previousSearchItem";
32
+ hotkeys: "openSearch" | "closeSearch" | "submitSearch" | "nextSearchItem" | "previousSearchItem";
33
33
  };
@@ -4,9 +4,11 @@ export const selectionFeature = {
4
4
  dependingFeatures: ["main", "tree"],
5
5
  getInitialState: (initialState) => (Object.assign({ selectedItems: [] }, initialState)),
6
6
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setSelectedItems: makeStateUpdater("selectedItems", tree) }, defaultConfig)),
7
+ stateHandlerNames: {
8
+ selectedItems: "setSelectedItems",
9
+ },
7
10
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { setSelectedItems: (selectedItems) => {
8
- var _a, _b;
9
- (_b = (_a = instance.getConfig()).setSelectedItems) === null || _b === void 0 ? void 0 : _b.call(_a, selectedItems);
11
+ instance.applySubStateUpdate("selectedItems", selectedItems);
10
12
  },
11
13
  // TODO memo
12
14
  getSelectedItems: () => {
@@ -48,7 +50,7 @@ export const selectionFeature = {
48
50
  else {
49
51
  item.select();
50
52
  }
51
- }, getProps: () => (Object.assign(Object.assign({}, prev.getProps()), { onClick: item.getMemoizedProp("selection/onClick", () => (e) => {
53
+ }, getProps: () => (Object.assign(Object.assign({}, prev.getProps()), { "aria-selected": item.isSelected() ? "true" : "false", onClick: item.getMemoizedProp("selection/onClick", () => (e) => {
52
54
  var _a, _b;
53
55
  if (e.shiftKey) {
54
56
  item.selectUpTo(e.ctrlKey || e.metaKey);
@@ -1,6 +1,12 @@
1
+ import { makeStateUpdater } from "../../utils";
1
2
  export const syncDataLoaderFeature = {
2
3
  key: "sync-data-loader",
3
4
  dependingFeatures: ["main"],
5
+ getInitialState: (initialState) => (Object.assign({ loadingItems: [] }, initialState)),
6
+ getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setLoadingItems: makeStateUpdater("loadingItems", tree) }, defaultConfig)),
7
+ stateHandlerNames: {
8
+ loadingItems: "setLoadingItems",
9
+ },
4
10
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: (itemId) => instance.getConfig().dataLoader.getItem(itemId), retrieveChildrenIds: (itemId) => instance.getConfig().dataLoader.getChildren(itemId) })),
5
11
  createItemInstance: (prev) => (Object.assign(Object.assign({}, prev), { isLoading: () => false })),
6
12
  };
@@ -6,7 +6,7 @@ export type SyncDataLoaderFeatureDef<T> = {
6
6
  state: {};
7
7
  config: {
8
8
  rootItemId: string;
9
- dataLoader: SyncTreeDataLoader<T>;
9
+ dataLoader?: SyncTreeDataLoader<T>;
10
10
  };
11
11
  treeInstance: {
12
12
  retrieveItemData: (itemId: string) => T;
@@ -13,6 +13,10 @@ export const treeFeature = {
13
13
  dependingFeatures: ["main"],
14
14
  getInitialState: (initialState) => (Object.assign({ expandedItems: [], focusedItem: null }, initialState)),
15
15
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setExpandedItems: makeStateUpdater("expandedItems", tree), setFocusedItem: makeStateUpdater("focusedItem", tree) }, defaultConfig)),
16
+ stateHandlerNames: {
17
+ expandedItems: "setExpandedItems",
18
+ focusedItem: "setFocusedItem",
19
+ },
16
20
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: () => {
17
21
  throw new Error("No data-loader registered");
18
22
  }, retrieveChildrenIds: () => {
@@ -20,7 +24,6 @@ export const treeFeature = {
20
24
  }, isItemExpanded: (itemId) => instance.getState().expandedItems.includes(itemId), getItemsMeta: () => {
21
25
  const { rootItemId } = instance.getConfig();
22
26
  const { expandedItems } = instance.getState();
23
- // console.log("!", instance.getConfig());
24
27
  const flatItems = [];
25
28
  const recursiveAdd = (itemId, parentId, level, setSize, posInSet) => {
26
29
  var _a;
@@ -47,23 +50,23 @@ export const treeFeature = {
47
50
  }
48
51
  return flatItems;
49
52
  }, expandItem: (itemId) => {
50
- var _a, _b, _c;
53
+ var _a;
51
54
  if (!instance.getItemInstance(itemId).isFolder()) {
52
55
  return;
53
56
  }
54
57
  if ((_a = instance.getState().loadingItems) === null || _a === void 0 ? void 0 : _a.includes(itemId)) {
55
58
  return;
56
59
  }
57
- (_c = (_b = instance
58
- .getConfig()).setExpandedItems) === null || _c === void 0 ? void 0 : _c.call(_b, (expandedItems) => [...expandedItems, itemId]);
60
+ instance.applySubStateUpdate("expandedItems", (expandedItems) => [
61
+ ...expandedItems,
62
+ itemId,
63
+ ]);
59
64
  instance.rebuildTree();
60
65
  }, collapseItem: (itemId) => {
61
- var _a, _b;
62
66
  if (!instance.getItemInstance(itemId).isFolder()) {
63
67
  return;
64
68
  }
65
- (_b = (_a = instance
66
- .getConfig()).setExpandedItems) === null || _b === void 0 ? void 0 : _b.call(_a, (expandedItems) => expandedItems.filter((id) => id !== itemId));
69
+ instance.applySubStateUpdate("expandedItems", (expandedItems) => expandedItems.filter((id) => id !== itemId));
67
70
  instance.rebuildTree();
68
71
  },
69
72
  // TODO memo
@@ -71,8 +74,7 @@ export const treeFeature = {
71
74
  var _a, _b;
72
75
  return ((_b = instance.getItemInstance((_a = instance.getState().focusedItem) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : instance.getItems()[0]);
73
76
  }, focusItem: (itemId) => {
74
- var _a, _b;
75
- (_b = (_a = instance.getConfig()).setFocusedItem) === null || _b === void 0 ? void 0 : _b.call(_a, itemId);
77
+ instance.applySubStateUpdate("focusedItem", itemId);
76
78
  }, focusNextItem: () => {
77
79
  const { index } = instance.getFocusedItem().getItemMeta();
78
80
  const nextIndex = Math.min(index + 1, instance.getItems().length - 1);
@@ -81,7 +83,7 @@ export const treeFeature = {
81
83
  const { index } = instance.getFocusedItem().getItemMeta();
82
84
  const nextIndex = Math.max(index - 1, 0);
83
85
  instance.focusItem(instance.getItems()[nextIndex].getId());
84
- }, updateDomFocus: (scrollIntoView) => {
86
+ }, updateDomFocus: () => {
85
87
  // Required because if the state is managed outside in react, the state only updated during next render
86
88
  setTimeout(() => __awaiter(void 0, void 0, void 0, function* () {
87
89
  var _a, _b;
@@ -92,9 +94,6 @@ export const treeFeature = {
92
94
  if (!focusedElement)
93
95
  return;
94
96
  focusedElement.focus();
95
- // if (scrollIntoView) {
96
- // focusedElement.scrollIntoView();
97
- // }
98
97
  }));
99
98
  }, getContainerProps: () => {
100
99
  var _a;
@@ -102,11 +101,15 @@ export const treeFeature = {
102
101
  } })),
103
102
  createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { isLoading: () => {
104
103
  throw new Error("No data-loader registered");
105
- }, getId: () => item.getItemMeta().itemId, getProps: () => {
104
+ }, scrollTo: (scrollIntoViewArg) => __awaiter(void 0, void 0, void 0, function* () {
105
+ var _a, _b;
106
+ (_b = (_a = tree.getConfig()).scrollToItem) === null || _b === void 0 ? void 0 : _b.call(_a, item);
107
+ yield poll(() => item.getElement() !== null, 20);
108
+ item.getElement().scrollIntoView(scrollIntoViewArg);
109
+ }), getId: () => item.getItemMeta().itemId, getProps: () => {
106
110
  var _a;
107
111
  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());
112
+ 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
113
  item.setFocused();
111
114
  item.primaryAction();
112
115
  if (e.ctrlKey || e.shiftKey || e.metaKey) {
@@ -135,12 +138,9 @@ export const treeFeature = {
135
138
  }
136
139
  }
137
140
  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
141
+ }, () => [item.getItemMeta()]),
142
+ // TODO remove
143
+ getIndexInParent: () => item.getItemMeta().posInSet, getChildren: () => tree
144
144
  .retrieveChildrenIds(item.getItemMeta().itemId)
145
145
  .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
146
  var _a, _b, _c, _d, _e;
@@ -203,7 +203,7 @@ export const treeFeature = {
203
203
  if ((!item.isExpanded() || !item.isFolder()) &&
204
204
  item.getItemMeta().level !== 0) {
205
205
  (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.setFocused();
206
- tree.updateDomFocus(true);
206
+ tree.updateDomFocus();
207
207
  }
208
208
  else {
209
209
  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";
@@ -52,6 +52,7 @@ export type CustomHotkeysConfig<T, F extends FeatureDef = FeatureDefs<T>> = Part
52
52
  export type FeatureImplementation<T = any, D extends FeatureDef = any, F extends FeatureDef = EmptyFeatureDef> = {
53
53
  key?: string;
54
54
  dependingFeatures?: string[];
55
+ stateHandlerNames?: Partial<Record<keyof MergedFeatures<F>["state"], keyof MergedFeatures<F>["config"]>>;
55
56
  getInitialState?: (initialState: Partial<MergedFeatures<F>["state"]>, tree: MergedFeatures<F>["treeInstance"]) => Partial<D["state"] & MergedFeatures<F>["state"]>;
56
57
  getDefaultConfig?: (defaultConfig: Partial<MergedFeatures<F>["config"]>, tree: MergedFeatures<F>["treeInstance"]) => Partial<D["config"] & MergedFeatures<F>["config"]>;
57
58
  createTreeInstance?: (prev: MergedFeatures<F>["treeInstance"], instance: MergedFeatures<F>["treeInstance"]) => D["treeInstance"] & MergedFeatures<F>["treeInstance"];
@@ -60,9 +61,6 @@ export type FeatureImplementation<T = any, D extends FeatureDef = any, F extends
60
61
  onTreeUnmount?: (instance: MergedFeatures<F>["treeInstance"], treeElement: HTMLElement) => void;
61
62
  onItemMount?: (instance: MergedFeatures<F>["itemInstance"], itemElement: HTMLElement, tree: MergedFeatures<F>["treeInstance"]) => void;
62
63
  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
64
  hotkeys?: HotkeysConfig<T, D>;
67
65
  };
68
66
  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.6",
4
4
  "main": "lib/cjs/index.js",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/esm/index.d.ts",