@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
@@ -5,16 +5,30 @@ export type DndDataRef = {
5
5
  };
6
6
 
7
7
  export type DndState<T> = {
8
- draggedItems?: ItemInstance<T>[];
8
+ draggedItems?: ItemInstance<T>[]; // TODO not used anymore?
9
9
  draggingOverItem?: ItemInstance<T>;
10
10
  dragTarget?: DropTarget<T>;
11
11
  };
12
12
 
13
- export type DropTarget<T> = {
14
- item: ItemInstance<T>;
15
- childIndex: number | null;
13
+ export type DragLineData = {
14
+ intend: number;
15
+ top: number;
16
+ left: number;
17
+ right: number;
16
18
  };
17
19
 
20
+ export type DropTarget<T> =
21
+ | {
22
+ item: ItemInstance<T>;
23
+ childIndex: number;
24
+ insertionIndex: number;
25
+ }
26
+ | {
27
+ item: ItemInstance<T>;
28
+ childIndex: null;
29
+ insertionIndex: null;
30
+ };
31
+
18
32
  export enum DropTargetPosition {
19
33
  Top = "top",
20
34
  Bottom = "bottom",
@@ -44,15 +58,26 @@ export type DragAndDropFeatureDef<T> = {
44
58
  dataTransfer: DataTransfer,
45
59
  target: DropTarget<T>
46
60
  ) => boolean;
47
-
48
61
  onDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => void;
49
62
  onDropForeignDragObject?: (
50
63
  dataTransfer: DataTransfer,
51
64
  target: DropTarget<T>
52
65
  ) => void;
66
+
67
+ /** Runs in the onDragEnd event, if `ev.dataTransfer.dropEffect` is not `none`, i.e. the drop
68
+ * was not aborted. No target is provided as parameter since the target may be a foreign drop target.
69
+ * This is useful to seperate out the logic to move dragged items out of their previous parents.
70
+ * Use `onDrop` to handle drop-related logic.
71
+ *
72
+ * This ignores the `canDrop` handler, since the drop target is unknown in this handler.
73
+ */
74
+ // onSuccessfulDragEnd?: (items: ItemInstance<T>[]) => void;
75
+
76
+ onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
53
77
  };
54
78
  treeInstance: {
55
79
  getDropTarget: () => DropTarget<T> | null;
80
+ getDragLineData: () => DragLineData | null;
56
81
  };
57
82
  itemInstance: {
58
83
  isDropTarget: () => boolean;
@@ -1,5 +1,10 @@
1
1
  import { ItemInstance, TreeInstance } from "../../types/core";
2
- import { DropTarget, DropTargetPosition } from "./types";
2
+ import {
3
+ DndState,
4
+ DragAndDropFeatureDef,
5
+ DropTarget,
6
+ DropTargetPosition,
7
+ } from "./types";
3
8
 
4
9
  export const getDragCode = ({ item, childIndex }: DropTarget<any>) =>
5
10
  `${item.getId()}__${childIndex ?? "none"}`;
@@ -49,40 +54,64 @@ const getDropTargetPosition = (
49
54
  export const getDropTarget = (
50
55
  e: any,
51
56
  item: ItemInstance<any>,
52
- tree: TreeInstance<any>
57
+ tree: TreeInstance<any>,
58
+ canDropInbetween = tree.getConfig().canDropInbetween
53
59
  ): DropTarget<any> => {
54
60
  const config = tree.getConfig();
55
- const offset = getDropOffset(e, item);
61
+ const draggedItems = tree.getState().dnd?.draggedItems ?? [];
62
+ const itemTarget = { item, childIndex: null, insertionIndex: null };
63
+ const parentTarget = {
64
+ item: item.getParent(),
65
+ childIndex: null,
66
+ insertionIndex: null,
67
+ };
56
68
 
57
- const dropOnItemTarget = { item, childIndex: null };
69
+ if (!canDropInbetween) {
70
+ if (!canDrop(e.dataTransfer, parentTarget, tree)) {
71
+ return getDropTarget(e, item.getParent(), tree, false);
72
+ }
73
+ return itemTarget;
74
+ }
58
75
 
59
- const pos = getDropTargetPosition(
60
- offset,
61
- config.topLinePercentage ?? 0.3,
62
- config.bottomLinePercentage ?? 0.7
63
- );
64
- const inbetweenPos = getDropTargetPosition(offset, 0.5, 0.5);
76
+ const canDropInside = canDrop(e.dataTransfer, itemTarget, tree);
65
77
 
66
- if (!config.canDropInbetween) {
67
- return dropOnItemTarget;
68
- }
78
+ const offset = getDropOffset(e, item);
69
79
 
70
- if (!canDrop(e.dataTransfer, dropOnItemTarget, tree)) {
71
- return {
72
- item: item.getParent(),
73
- childIndex:
74
- item.getIndexInParent() +
75
- (inbetweenPos === DropTargetPosition.Top ? 0 : 1),
76
- };
77
- }
80
+ const pos = canDropInside
81
+ ? getDropTargetPosition(
82
+ offset,
83
+ config.topLinePercentage ?? 0.3,
84
+ config.bottomLinePercentage ?? 0.7
85
+ )
86
+ : getDropTargetPosition(offset, 0.5, 0.5);
78
87
 
79
88
  if (pos === DropTargetPosition.Item) {
80
- return dropOnItemTarget;
89
+ return itemTarget;
90
+ }
91
+
92
+ if (!canDrop(e.dataTransfer, parentTarget, tree)) {
93
+ return getDropTarget(e, item.getParent(), tree, false);
81
94
  }
82
95
 
96
+ const childIndex =
97
+ item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1);
98
+
99
+ const numberOfDragItemsBeforeTarget = item
100
+ .getParent()
101
+ .getChildren()
102
+ .slice(0, childIndex)
103
+ .reduce(
104
+ (counter, child) =>
105
+ child && draggedItems?.some((i) => i.getId() === child.getId())
106
+ ? ++counter
107
+ : counter,
108
+ 0
109
+ );
110
+
83
111
  return {
84
112
  item: item.getParent(),
85
- childIndex:
86
- item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1),
113
+ childIndex,
114
+ // TODO performance could be improved by computing this only when dragcode changed
115
+ insertionIndex: childIndex - numberOfDragItemsBeforeTarget,
87
116
  };
88
117
  };
@@ -14,7 +14,6 @@ export const expandAllFeature: FeatureImplementation<
14
14
  | ExpandAllFeatureDef
15
15
  > = {
16
16
  key: "expand-all",
17
- dependingFeatures: ["main", "tree"],
18
17
 
19
18
  createTreeInstance: (prev, tree) => ({
20
19
  ...prev,
@@ -25,8 +24,9 @@ export const expandAllFeature: FeatureImplementation<
25
24
  );
26
25
  },
27
26
 
28
- collapseAll: async () => {
29
- tree.getConfig().setExpandedItems?.([]);
27
+ collapseAll: () => {
28
+ tree.applySubStateUpdate("expandedItems", []);
29
+ tree.rebuildTree();
30
30
  },
31
31
  }),
32
32
 
@@ -37,6 +37,9 @@ export const expandAllFeature: FeatureImplementation<
37
37
  if (cancelToken?.current) {
38
38
  return;
39
39
  }
40
+ if (!item.isFolder()) {
41
+ return;
42
+ }
40
43
 
41
44
  item.expand();
42
45
  await poll(() => !tree.getState().loadingItems.includes(item.getId()));
@@ -50,12 +53,10 @@ export const expandAllFeature: FeatureImplementation<
50
53
  );
51
54
  },
52
55
 
53
- collapseAll: async () => {
54
- await Promise.all(
55
- item.getChildren().map(async (child) => {
56
- await child?.collapseAll();
57
- })
58
- );
56
+ collapseAll: () => {
57
+ for (const child of item.getChildren()) {
58
+ child?.collapseAll();
59
+ }
59
60
  item.collapse();
60
61
  },
61
62
  }),
@@ -3,11 +3,11 @@ export type ExpandAllFeatureDef = {
3
3
  config: {};
4
4
  treeInstance: {
5
5
  expandAll: (cancelToken?: { current: boolean }) => Promise<void>;
6
- collapseAll: () => Promise<void>;
6
+ collapseAll: () => void;
7
7
  };
8
8
  itemInstance: {
9
9
  expandAll: (cancelToken?: { current: boolean }) => Promise<void>;
10
- collapseAll: () => Promise<void>;
10
+ collapseAll: () => void;
11
11
  };
12
12
  hotkeys: never;
13
13
  };
@@ -48,7 +48,6 @@ export const hotkeysCoreFeature: FeatureImplementation<
48
48
  MainFeatureDef | HotkeysCoreFeatureDef<any>
49
49
  > = {
50
50
  key: "hotkeys-core",
51
- dependingFeatures: ["main", "tree"],
52
51
 
53
52
  onTreeMount: (tree, element) => {
54
53
  const data = tree.getDataRef<HotkeysCoreDataRef>();
@@ -5,6 +5,7 @@ import {
5
5
  SetStateFn,
6
6
  TreeConfig,
7
7
  TreeState,
8
+ Updater,
8
9
  } from "../../types/core";
9
10
  import { ItemMeta } from "../tree/types";
10
11
 
@@ -17,6 +18,11 @@ export type MainFeatureDef<T = any> = {
17
18
  setState?: SetStateFn<TreeState<T>>;
18
19
  };
19
20
  treeInstance: {
21
+ /** @internal */
22
+ applySubStateUpdate: <K extends keyof TreeState<any>>(
23
+ stateName: K,
24
+ updater: Updater<TreeState<T>[K]>
25
+ ) => void;
20
26
  setState: SetStateFn<TreeState<T>>;
21
27
  getState: () => TreeState<T>;
22
28
  setConfig: SetStateFn<TreeConfig<T>>;
@@ -10,7 +10,6 @@ export const renamingFeature: FeatureImplementation<
10
10
  MainFeatureDef | TreeFeatureDef<any> | RenamingFeatureDef<any>
11
11
  > = {
12
12
  key: "renaming",
13
- dependingFeatures: ["main", "tree"],
14
13
 
15
14
  getDefaultConfig: (defaultConfig, tree) => ({
16
15
  setRenamingItem: makeStateUpdater("renamingItem", tree),
@@ -19,19 +18,23 @@ export const renamingFeature: FeatureImplementation<
19
18
  ...defaultConfig,
20
19
  }),
21
20
 
21
+ stateHandlerNames: {
22
+ renamingItem: "setRenamingItem",
23
+ renamingValue: "setRenamingValue",
24
+ },
25
+
22
26
  createTreeInstance: (prev, instance) => ({
23
27
  ...prev,
24
28
 
25
29
  startRenamingItem: (itemId) => {
26
- const config = instance.getConfig();
27
30
  const item = instance.getItemInstance(itemId);
28
31
 
29
32
  if (!item.canRename()) {
30
33
  return;
31
34
  }
32
35
 
33
- config.setRenamingItem?.(itemId);
34
- config.setRenamingValue?.(item.getItemName());
36
+ instance.applySubStateUpdate("renamingItem", itemId);
37
+ instance.applySubStateUpdate("renamingValue", item.getItemName());
35
38
  },
36
39
 
37
40
  getRenamingItem: () => {
@@ -42,7 +45,7 @@ export const renamingFeature: FeatureImplementation<
42
45
  getRenamingValue: () => instance.getState().renamingValue || "",
43
46
 
44
47
  abortRenaming: () => {
45
- instance.getConfig().setRenamingItem?.(null);
48
+ instance.applySubStateUpdate("renamingItem", null);
46
49
  },
47
50
 
48
51
  completeRenaming: () => {
@@ -51,7 +54,7 @@ export const renamingFeature: FeatureImplementation<
51
54
  if (item) {
52
55
  config.onRename?.(item, instance.getState().renamingValue || "");
53
56
  }
54
- instance.getConfig().setRenamingItem?.(null);
57
+ instance.applySubStateUpdate("renamingItem", null);
55
58
  },
56
59
 
57
60
  isRenamingItem: () => !!instance.getState().renamingItem,
@@ -63,7 +66,7 @@ export const renamingFeature: FeatureImplementation<
63
66
  onBlur: () => tree.abortRenaming(),
64
67
  value: tree.getRenamingValue(),
65
68
  onChange: (e) => {
66
- tree.getConfig().setRenamingValue?.(e.target.value);
69
+ tree.applySubStateUpdate("renamingValue", e.target.value);
67
70
  },
68
71
  }),
69
72
 
@@ -10,7 +10,6 @@ export const searchFeature: FeatureImplementation<
10
10
  MainFeatureDef | TreeFeatureDef<any> | SearchFeatureDef<any>
11
11
  > = {
12
12
  key: "search",
13
- dependingFeatures: ["main", "tree"],
14
13
 
15
14
  getInitialState: (initialState) => ({
16
15
  search: null,
@@ -25,11 +24,15 @@ export const searchFeature: FeatureImplementation<
25
24
  ...defaultConfig,
26
25
  }),
27
26
 
27
+ stateHandlerNames: {
28
+ search: "setSearch",
29
+ },
30
+
28
31
  createTreeInstance: (prev, instance) => ({
29
32
  ...prev,
30
33
 
31
34
  setSearch: (search) => {
32
- instance.getConfig().setSearch?.(search);
35
+ instance.applySubStateUpdate("search", search);
33
36
  instance
34
37
  .getItems()
35
38
  .find((item) =>
@@ -71,8 +74,9 @@ export const searchFeature: FeatureImplementation<
71
74
 
72
75
  getSearchMatchingItems: memo(
73
76
  (search, items) =>
74
- items.filter((item) =>
75
- instance.getConfig().isSearchMatchingItem?.(search, item)
77
+ items.filter(
78
+ (item) =>
79
+ search && instance.getConfig().isSearchMatchingItem?.(search, item)
76
80
  ),
77
81
  () => [instance.getSearchValue(), instance.getItems()]
78
82
  ),
@@ -105,12 +109,22 @@ export const searchFeature: FeatureImplementation<
105
109
  },
106
110
  },
107
111
 
112
+ submitSearch: {
113
+ hotkey: "Enter",
114
+ allowWhenInputFocused: true,
115
+ isEnabled: (tree) => tree.isSearchOpen(),
116
+ handler: (e, tree) => {
117
+ tree.closeSearch();
118
+ tree.setSelectedItems([tree.getFocusedItem().getId()]);
119
+ },
120
+ },
121
+
108
122
  nextSearchItem: {
109
123
  hotkey: "ArrowDown",
110
124
  allowWhenInputFocused: true,
125
+ canRepeat: true,
111
126
  isEnabled: (tree) => tree.isSearchOpen(),
112
127
  handler: (e, tree) => {
113
- // TODO scroll into view
114
128
  const focusItem = tree
115
129
  .getSearchMatchingItems()
116
130
  .find(
@@ -119,15 +133,16 @@ export const searchFeature: FeatureImplementation<
119
133
  tree.getFocusedItem().getItemMeta().index
120
134
  );
121
135
  focusItem?.setFocused();
136
+ focusItem?.scrollTo({ block: "nearest", inline: "nearest" });
122
137
  },
123
138
  },
124
139
 
125
140
  previousSearchItem: {
126
141
  hotkey: "ArrowUp",
127
142
  allowWhenInputFocused: true,
143
+ canRepeat: true,
128
144
  isEnabled: (tree) => tree.isSearchOpen(),
129
145
  handler: (e, tree) => {
130
- // TODO scroll into view
131
146
  const focusItem = [...tree.getSearchMatchingItems()]
132
147
  .reverse()
133
148
  .find(
@@ -136,6 +151,7 @@ export const searchFeature: FeatureImplementation<
136
151
  tree.getFocusedItem().getItemMeta().index
137
152
  );
138
153
  focusItem?.setFocused();
154
+ focusItem?.scrollTo({ block: "nearest", inline: "nearest" });
139
155
  },
140
156
  },
141
157
  },
@@ -34,6 +34,7 @@ export type SearchFeatureDef<T> = {
34
34
  hotkeys:
35
35
  | "openSearch"
36
36
  | "closeSearch"
37
+ | "submitSearch"
37
38
  | "nextSearchItem"
38
39
  | "previousSearchItem";
39
40
  };
@@ -10,7 +10,6 @@ export const selectionFeature: FeatureImplementation<
10
10
  MainFeatureDef | TreeFeatureDef<any> | SelectionFeatureDef<any>
11
11
  > = {
12
12
  key: "selection",
13
- dependingFeatures: ["main", "tree"],
14
13
 
15
14
  getInitialState: (initialState) => ({
16
15
  selectedItems: [],
@@ -22,11 +21,15 @@ export const selectionFeature: FeatureImplementation<
22
21
  ...defaultConfig,
23
22
  }),
24
23
 
24
+ stateHandlerNames: {
25
+ selectedItems: "setSelectedItems",
26
+ },
27
+
25
28
  createTreeInstance: (prev, instance) => ({
26
29
  ...prev,
27
30
 
28
31
  setSelectedItems: (selectedItems) => {
29
- instance.getConfig().setSelectedItems?.(selectedItems);
32
+ instance.applySubStateUpdate("selectedItems", selectedItems);
30
33
  },
31
34
 
32
35
  // TODO memo
@@ -67,7 +70,7 @@ export const selectionFeature: FeatureImplementation<
67
70
  const newSelectedItems = tree
68
71
  .getItems()
69
72
  .slice(a, b + 1)
70
- .map((item) => item.getItemMeta().itemId);
73
+ .map((treeItem) => treeItem.getItemMeta().itemId);
71
74
 
72
75
  if (!ctrl) {
73
76
  tree.setSelectedItems(newSelectedItems);
@@ -91,6 +94,7 @@ export const selectionFeature: FeatureImplementation<
91
94
 
92
95
  getProps: () => ({
93
96
  ...prev.getProps(),
97
+ "aria-selected": item.isSelected() ? "true" : "false",
94
98
  onClick: item.getMemoizedProp("selection/onClick", () => (e) => {
95
99
  if (e.shiftKey) {
96
100
  item.selectUpTo(e.ctrlKey || e.metaKey);
@@ -1,6 +1,7 @@
1
1
  import { FeatureImplementation } from "../../types/core";
2
2
  import { SyncDataLoaderFeatureDef } from "./types";
3
3
  import { MainFeatureDef } from "../main/types";
4
+ import { makeStateUpdater } from "../../utils";
4
5
 
5
6
  export const syncDataLoaderFeature: FeatureImplementation<
6
7
  any,
@@ -8,16 +9,29 @@ export const syncDataLoaderFeature: FeatureImplementation<
8
9
  MainFeatureDef | SyncDataLoaderFeatureDef<any>
9
10
  > = {
10
11
  key: "sync-data-loader",
11
- dependingFeatures: ["main"],
12
+
13
+ getInitialState: (initialState) => ({
14
+ loadingItems: [],
15
+ ...initialState,
16
+ }),
17
+
18
+ getDefaultConfig: (defaultConfig, tree) => ({
19
+ setLoadingItems: makeStateUpdater("loadingItems", tree),
20
+ ...defaultConfig,
21
+ }),
22
+
23
+ stateHandlerNames: {
24
+ loadingItems: "setLoadingItems",
25
+ },
12
26
 
13
27
  createTreeInstance: (prev, instance) => ({
14
28
  ...prev,
15
29
 
16
30
  retrieveItemData: (itemId) =>
17
- instance.getConfig().dataLoader.getItem(itemId),
31
+ instance.getConfig().dataLoader!.getItem(itemId),
18
32
 
19
33
  retrieveChildrenIds: (itemId) =>
20
- instance.getConfig().dataLoader.getChildren(itemId),
34
+ instance.getConfig().dataLoader!.getChildren(itemId),
21
35
  }),
22
36
 
23
37
  createItemInstance: (prev) => ({
@@ -7,7 +7,7 @@ export type SyncDataLoaderFeatureDef<T> = {
7
7
  state: {};
8
8
  config: {
9
9
  rootItemId: string;
10
- dataLoader: SyncTreeDataLoader<T>;
10
+ dataLoader?: SyncTreeDataLoader<T>;
11
11
  };
12
12
  treeInstance: {
13
13
  retrieveItemData: (itemId: string) => T;
@@ -14,7 +14,6 @@ export const treeFeature: FeatureImplementation<
14
14
  | SyncDataLoaderFeatureDef<any>
15
15
  > = {
16
16
  key: "tree",
17
- dependingFeatures: ["main"],
18
17
 
19
18
  getInitialState: (initialState) => ({
20
19
  expandedItems: [],
@@ -28,6 +27,11 @@ export const treeFeature: FeatureImplementation<
28
27
  ...defaultConfig,
29
28
  }),
30
29
 
30
+ stateHandlerNames: {
31
+ expandedItems: "setExpandedItems",
32
+ focusedItem: "setFocusedItem",
33
+ },
34
+
31
35
  createTreeInstance: (prev, instance) => ({
32
36
  ...prev,
33
37
 
@@ -45,7 +49,6 @@ export const treeFeature: FeatureImplementation<
45
49
  getItemsMeta: () => {
46
50
  const { rootItemId } = instance.getConfig();
47
51
  const { expandedItems } = instance.getState();
48
- // console.log("!", instance.getConfig());
49
52
  const flatItems: ItemMeta[] = [];
50
53
 
51
54
  const recursiveAdd = (
@@ -91,9 +94,10 @@ export const treeFeature: FeatureImplementation<
91
94
  return;
92
95
  }
93
96
 
94
- instance
95
- .getConfig()
96
- .setExpandedItems?.((expandedItems) => [...expandedItems, itemId]);
97
+ instance.applySubStateUpdate("expandedItems", (expandedItems) => [
98
+ ...expandedItems,
99
+ itemId,
100
+ ]);
97
101
  instance.rebuildTree();
98
102
  },
99
103
 
@@ -102,11 +106,9 @@ export const treeFeature: FeatureImplementation<
102
106
  return;
103
107
  }
104
108
 
105
- instance
106
- .getConfig()
107
- .setExpandedItems?.((expandedItems) =>
108
- expandedItems.filter((id) => id !== itemId)
109
- );
109
+ instance.applySubStateUpdate("expandedItems", (expandedItems) =>
110
+ expandedItems.filter((id) => id !== itemId)
111
+ );
110
112
  instance.rebuildTree();
111
113
  },
112
114
 
@@ -119,7 +121,7 @@ export const treeFeature: FeatureImplementation<
119
121
  },
120
122
 
121
123
  focusItem: (itemId) => {
122
- instance.getConfig().setFocusedItem?.(itemId);
124
+ instance.applySubStateUpdate("focusedItem", itemId);
123
125
  },
124
126
 
125
127
  focusNextItem: () => {
@@ -134,7 +136,7 @@ export const treeFeature: FeatureImplementation<
134
136
  instance.focusItem(instance.getItems()[nextIndex].getId());
135
137
  },
136
138
 
137
- updateDomFocus: (scrollIntoView) => {
139
+ updateDomFocus: () => {
138
140
  // Required because if the state is managed outside in react, the state only updated during next render
139
141
  setTimeout(async () => {
140
142
  const focusedItem = instance.getFocusedItem();
@@ -143,9 +145,6 @@ export const treeFeature: FeatureImplementation<
143
145
  const focusedElement = focusedItem.getElement();
144
146
  if (!focusedElement) return;
145
147
  focusedElement.focus();
146
- // if (scrollIntoView) {
147
- // focusedElement.scrollIntoView();
148
- // }
149
148
  });
150
149
  },
151
150
 
@@ -162,6 +161,11 @@ export const treeFeature: FeatureImplementation<
162
161
  isLoading: () => {
163
162
  throw new Error("No data-loader registered");
164
163
  },
164
+ scrollTo: async (scrollIntoViewArg) => {
165
+ tree.getConfig().scrollToItem?.(item as any);
166
+ await poll(() => item.getElement() !== null, 20);
167
+ item.getElement()!.scrollIntoView(scrollIntoViewArg);
168
+ },
165
169
  getId: () => item.getItemMeta().itemId,
166
170
  getProps: () => {
167
171
  const itemMeta = item.getItemMeta();
@@ -170,12 +174,11 @@ export const treeFeature: FeatureImplementation<
170
174
  role: "treeitem",
171
175
  "aria-setsize": itemMeta.setSize,
172
176
  "aria-posinset": itemMeta.posInSet,
173
- "aria-selected": false,
177
+ "aria-selected": "false",
174
178
  "aria-label": item.getItemName(),
175
179
  "aria-level": itemMeta.level,
176
180
  tabIndex: item.isFocused() ? 0 : -1,
177
181
  onClick: item.getMemoizedProp("tree/onClick", () => (e) => {
178
- console.log("onClick", item.getId());
179
182
  item.setFocused();
180
183
  item.primaryAction();
181
184
 
@@ -225,10 +228,8 @@ export const treeFeature: FeatureImplementation<
225
228
  },
226
229
  () => [item.getItemMeta()]
227
230
  ),
228
- getIndexInParent: () =>
229
- item.getItemMeta().index -
230
- (item.getParent()?.getItemMeta().index ?? 0) -
231
- 1,
231
+ // TODO remove
232
+ getIndexInParent: () => item.getItemMeta().posInSet,
232
233
  getChildren: () =>
233
234
  tree
234
235
  .retrieveChildrenIds(item.getItemMeta().itemId)
@@ -301,7 +302,7 @@ export const treeFeature: FeatureImplementation<
301
302
  item.getItemMeta().level !== 0
302
303
  ) {
303
304
  item.getParent()?.setFocused();
304
- tree.updateDomFocus(true);
305
+ tree.updateDomFocus();
305
306
  } else {
306
307
  item.collapse();
307
308
  }
@@ -41,8 +41,7 @@ export type TreeFeatureDef<T> = {
41
41
  getFocusedItem: () => ItemInstance<any>;
42
42
  focusNextItem: () => void;
43
43
  focusPreviousItem: () => void;
44
- scrollToItem: (item: ItemInstance<any>) => void;
45
- updateDomFocus: (scrollIntoView?: boolean) => void;
44
+ updateDomFocus: () => void;
46
45
 
47
46
  getContainerProps: () => Record<string, any>;
48
47
  };
@@ -65,6 +64,9 @@ export type TreeFeatureDef<T> = {
65
64
  getItemAbove: () => ItemInstance<T> | null;
66
65
  getItemBelow: () => ItemInstance<T> | null;
67
66
  getMemoizedProp: <X>(name: string, create: () => X, deps?: any[]) => X;
67
+ scrollTo: (
68
+ scrollIntoViewArg?: boolean | ScrollIntoViewOptions
69
+ ) => Promise<void>;
68
70
  };
69
71
  hotkeys:
70
72
  | "focusNextItem"
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./types/core";
2
2
  export * from "./core/create-tree";
3
+
3
4
  export * from "./features/tree/types";
4
5
  export * from "./features/main/types";
5
6
  export * from "./features/drag-and-drop/types";
@@ -20,4 +21,6 @@ export * from "./features/search/feature";
20
21
  export * from "./features/renaming/feature";
21
22
  export * from "./features/expand-all/feature";
22
23
 
23
- export * from "./data-adapters/nested-data-adapter";
24
+ export * from "./utilities/create-on-drop-handler";
25
+ export * from "./utilities/insert-items-at-target";
26
+ export * from "./utilities/remove-items-from-parents";