@headless-tree/core 0.0.13 → 0.0.15

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 (125) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/cjs/core/create-tree.js +13 -4
  3. package/lib/cjs/features/async-data-loader/feature.js +73 -48
  4. package/lib/cjs/features/async-data-loader/types.d.ts +17 -14
  5. package/lib/cjs/features/drag-and-drop/feature.js +98 -93
  6. package/lib/cjs/features/drag-and-drop/types.d.ts +17 -29
  7. package/lib/cjs/features/drag-and-drop/types.js +7 -7
  8. package/lib/cjs/features/drag-and-drop/utils.d.ts +18 -3
  9. package/lib/cjs/features/drag-and-drop/utils.js +49 -51
  10. package/lib/cjs/features/expand-all/feature.js +26 -3
  11. package/lib/cjs/features/expand-all/types.d.ts +3 -1
  12. package/lib/cjs/features/hotkeys-core/feature.js +7 -3
  13. package/lib/cjs/features/hotkeys-core/types.d.ts +4 -5
  14. package/lib/cjs/features/keyboard-drag-and-drop/feature.d.ts +2 -0
  15. package/lib/cjs/features/keyboard-drag-and-drop/feature.js +207 -0
  16. package/lib/cjs/features/keyboard-drag-and-drop/types.d.ts +27 -0
  17. package/lib/cjs/features/keyboard-drag-and-drop/types.js +11 -0
  18. package/lib/cjs/features/prop-memoization/feature.js +2 -2
  19. package/lib/cjs/features/prop-memoization/types.d.ts +2 -2
  20. package/lib/cjs/features/renaming/feature.js +1 -1
  21. package/lib/cjs/features/search/feature.js +2 -0
  22. package/lib/cjs/features/search/types.d.ts +2 -2
  23. package/lib/cjs/features/selection/feature.js +4 -4
  24. package/lib/cjs/features/selection/types.d.ts +1 -1
  25. package/lib/cjs/features/sync-data-loader/feature.js +31 -5
  26. package/lib/cjs/features/sync-data-loader/types.d.ts +5 -5
  27. package/lib/cjs/features/tree/feature.js +4 -7
  28. package/lib/cjs/features/tree/types.d.ts +7 -5
  29. package/lib/cjs/index.d.ts +2 -0
  30. package/lib/cjs/index.js +2 -0
  31. package/lib/cjs/mddocs-entry.d.ts +10 -0
  32. package/lib/cjs/test-utils/test-tree-do.d.ts +2 -2
  33. package/lib/cjs/test-utils/test-tree-do.js +19 -6
  34. package/lib/cjs/test-utils/test-tree-expect.d.ts +5 -3
  35. package/lib/cjs/test-utils/test-tree-expect.js +3 -0
  36. package/lib/cjs/test-utils/test-tree.d.ts +2 -1
  37. package/lib/cjs/test-utils/test-tree.js +24 -21
  38. package/lib/cjs/types/core.d.ts +2 -1
  39. package/lib/cjs/utilities/create-on-drop-handler.d.ts +2 -2
  40. package/lib/cjs/utilities/create-on-drop-handler.js +13 -4
  41. package/lib/cjs/utilities/insert-items-at-target.d.ts +2 -2
  42. package/lib/cjs/utilities/insert-items-at-target.js +21 -12
  43. package/lib/cjs/utilities/remove-items-from-parents.d.ts +1 -1
  44. package/lib/cjs/utilities/remove-items-from-parents.js +12 -3
  45. package/lib/esm/core/create-tree.js +13 -4
  46. package/lib/esm/features/async-data-loader/feature.js +73 -48
  47. package/lib/esm/features/async-data-loader/types.d.ts +17 -14
  48. package/lib/esm/features/drag-and-drop/feature.js +99 -94
  49. package/lib/esm/features/drag-and-drop/types.d.ts +17 -29
  50. package/lib/esm/features/drag-and-drop/types.js +6 -6
  51. package/lib/esm/features/drag-and-drop/utils.d.ts +18 -3
  52. package/lib/esm/features/drag-and-drop/utils.js +44 -49
  53. package/lib/esm/features/expand-all/feature.js +26 -3
  54. package/lib/esm/features/expand-all/types.d.ts +3 -1
  55. package/lib/esm/features/hotkeys-core/feature.js +7 -3
  56. package/lib/esm/features/hotkeys-core/types.d.ts +4 -5
  57. package/lib/esm/features/keyboard-drag-and-drop/feature.d.ts +2 -0
  58. package/lib/esm/features/keyboard-drag-and-drop/feature.js +204 -0
  59. package/lib/esm/features/keyboard-drag-and-drop/types.d.ts +27 -0
  60. package/lib/esm/features/keyboard-drag-and-drop/types.js +8 -0
  61. package/lib/esm/features/prop-memoization/feature.js +2 -2
  62. package/lib/esm/features/prop-memoization/types.d.ts +2 -2
  63. package/lib/esm/features/renaming/feature.js +1 -1
  64. package/lib/esm/features/search/feature.js +2 -0
  65. package/lib/esm/features/search/types.d.ts +2 -2
  66. package/lib/esm/features/selection/feature.js +4 -4
  67. package/lib/esm/features/selection/types.d.ts +1 -1
  68. package/lib/esm/features/sync-data-loader/feature.js +31 -5
  69. package/lib/esm/features/sync-data-loader/types.d.ts +5 -5
  70. package/lib/esm/features/tree/feature.js +4 -7
  71. package/lib/esm/features/tree/types.d.ts +7 -5
  72. package/lib/esm/index.d.ts +2 -0
  73. package/lib/esm/index.js +2 -0
  74. package/lib/esm/mddocs-entry.d.ts +10 -0
  75. package/lib/esm/test-utils/test-tree-do.d.ts +2 -2
  76. package/lib/esm/test-utils/test-tree-do.js +19 -6
  77. package/lib/esm/test-utils/test-tree-expect.d.ts +5 -3
  78. package/lib/esm/test-utils/test-tree-expect.js +3 -0
  79. package/lib/esm/test-utils/test-tree.d.ts +2 -1
  80. package/lib/esm/test-utils/test-tree.js +24 -21
  81. package/lib/esm/types/core.d.ts +2 -1
  82. package/lib/esm/utilities/create-on-drop-handler.d.ts +2 -2
  83. package/lib/esm/utilities/create-on-drop-handler.js +13 -4
  84. package/lib/esm/utilities/insert-items-at-target.d.ts +2 -2
  85. package/lib/esm/utilities/insert-items-at-target.js +21 -12
  86. package/lib/esm/utilities/remove-items-from-parents.d.ts +1 -1
  87. package/lib/esm/utilities/remove-items-from-parents.js +12 -3
  88. package/package.json +2 -2
  89. package/src/core/core.spec.ts +31 -0
  90. package/src/core/create-tree.ts +15 -5
  91. package/src/features/async-data-loader/async-data-loader.spec.ts +10 -6
  92. package/src/features/async-data-loader/feature.ts +76 -48
  93. package/src/features/async-data-loader/types.ts +18 -11
  94. package/src/features/drag-and-drop/drag-and-drop.spec.ts +75 -89
  95. package/src/features/drag-and-drop/feature.ts +21 -22
  96. package/src/features/drag-and-drop/types.ts +23 -35
  97. package/src/features/drag-and-drop/utils.ts +67 -57
  98. package/src/features/expand-all/feature.ts +29 -5
  99. package/src/features/expand-all/types.ts +3 -1
  100. package/src/features/hotkeys-core/feature.ts +4 -0
  101. package/src/features/hotkeys-core/types.ts +4 -13
  102. package/src/features/keyboard-drag-and-drop/feature.ts +255 -0
  103. package/src/features/keyboard-drag-and-drop/keyboard-drag-and-drop.spec.ts +401 -0
  104. package/src/features/keyboard-drag-and-drop/types.ts +30 -0
  105. package/src/features/prop-memoization/feature.ts +2 -2
  106. package/src/features/prop-memoization/prop-memoization.spec.ts +2 -2
  107. package/src/features/prop-memoization/types.ts +2 -2
  108. package/src/features/renaming/feature.ts +8 -2
  109. package/src/features/search/feature.ts +2 -0
  110. package/src/features/search/types.ts +2 -2
  111. package/src/features/selection/feature.ts +4 -4
  112. package/src/features/selection/types.ts +1 -1
  113. package/src/features/sync-data-loader/feature.ts +26 -7
  114. package/src/features/sync-data-loader/types.ts +5 -5
  115. package/src/features/tree/feature.ts +8 -11
  116. package/src/features/tree/types.ts +7 -5
  117. package/src/index.ts +2 -0
  118. package/src/mddocs-entry.ts +16 -0
  119. package/src/test-utils/test-tree-do.ts +3 -3
  120. package/src/test-utils/test-tree-expect.ts +7 -2
  121. package/src/test-utils/test-tree.ts +26 -22
  122. package/src/types/core.ts +2 -0
  123. package/src/utilities/create-on-drop-handler.ts +4 -4
  124. package/src/utilities/insert-items-at-target.ts +18 -14
  125. package/src/utilities/remove-items-from-parents.ts +6 -3
@@ -17,8 +17,8 @@ const customFeature: FeatureImplementation = {
17
17
  }),
18
18
  },
19
19
  treeInstance: {
20
- getContainerProps: ({ prev }) => ({
21
- ...prev?.(),
20
+ getContainerProps: ({ prev }, treeLabel) => ({
21
+ ...prev?.(treeLabel),
22
22
  customValue: createTreeValue(),
23
23
  onCustomEvent: () => treeHandler(),
24
24
  }),
@@ -1,6 +1,6 @@
1
- export type PropMemoizationDataRef = {
1
+ export interface PropMemoizationDataRef {
2
2
  memoizedProps?: Record<string, any>;
3
- };
3
+ }
4
4
 
5
5
  export type PropMemoizationFeatureDef = {
6
6
  state: {};
@@ -1,6 +1,12 @@
1
1
  import { FeatureImplementation, ItemInstance } from "../../types/core";
2
2
  import { makeStateUpdater } from "../../utils";
3
3
 
4
+ type InputEvent = {
5
+ target?: {
6
+ value: string;
7
+ };
8
+ };
9
+
4
10
  export const renamingFeature: FeatureImplementation = {
5
11
  key: "renaming",
6
12
 
@@ -53,10 +59,10 @@ export const renamingFeature: FeatureImplementation = {
53
59
  },
54
60
 
55
61
  getRenameInputProps: ({ tree }) => ({
62
+ ref: (r: HTMLInputElement) => r?.focus(),
56
63
  onBlur: () => tree.abortRenaming(),
57
64
  value: tree.getRenamingValue(),
58
- onChange: (e: any) => {
59
- // TODO custom type with e.target.value
65
+ onChange: (e: InputEvent) => {
60
66
  tree.applySubStateUpdate("renamingValue", e.target?.value);
61
67
  },
62
68
  }),
@@ -56,10 +56,12 @@ export const searchFeature: FeatureImplementation = {
56
56
  getSearchInputElement: ({ tree }) =>
57
57
  tree.getDataRef<SearchFeatureDataRef>().current.searchInput ?? null,
58
58
 
59
+ // TODO memoize with propMemoizationFeature
59
60
  getSearchInputElementProps: ({ tree }) => ({
60
61
  value: tree.getSearchValue(),
61
62
  onChange: (e: any) => tree.setSearch(e.target.value),
62
63
  onBlur: () => tree.closeSearch(),
64
+ ref: tree.registerSearchInputElement,
63
65
  }),
64
66
 
65
67
  getSearchMatchingItems: memo(
@@ -1,10 +1,10 @@
1
1
  import { ItemInstance, SetStateFn } from "../../types/core";
2
2
  import { HotkeysCoreDataRef } from "../hotkeys-core/types";
3
3
 
4
- export type SearchFeatureDataRef<T = any> = HotkeysCoreDataRef & {
4
+ export interface SearchFeatureDataRef<T = any> extends HotkeysCoreDataRef {
5
5
  matchingItems: ItemInstance<T>[];
6
6
  searchInput: HTMLInputElement | null;
7
- };
7
+ }
8
8
 
9
9
  export type SearchFeatureDef<T> = {
10
10
  state: {
@@ -23,7 +23,6 @@ export const selectionFeature: FeatureImplementation = {
23
23
  tree.applySubStateUpdate("selectedItems", selectedItems);
24
24
  },
25
25
 
26
- // TODO memo
27
26
  getSelectedItems: ({ tree }) => {
28
27
  return tree.getState().selectedItems.map(tree.getItemInstance);
29
28
  },
@@ -103,9 +102,10 @@ export const selectionFeature: FeatureImplementation = {
103
102
  // tree.setSelectedItems([tree.getFocusedItem().getId()]);
104
103
  // },
105
104
  // },
106
- toggleSelectItem: {
107
- hotkey: "ctrl+space",
108
- handler: (e, tree) => {
105
+ toggleSelectedItem: {
106
+ hotkey: "Control+Space",
107
+ preventDefault: true,
108
+ handler: (_, tree) => {
109
109
  tree.getFocusedItem().toggleSelect();
110
110
  },
111
111
  },
@@ -19,7 +19,7 @@ export type SelectionFeatureDef<T> = {
19
19
  selectUpTo: (ctrl: boolean) => void;
20
20
  };
21
21
  hotkeys:
22
- | "toggleSelectItem"
22
+ | "toggleSelectedItem"
23
23
  | "selectUpwards"
24
24
  | "selectDownwards"
25
25
  | "selectAll";
@@ -1,29 +1,48 @@
1
1
  import { FeatureImplementation } from "../../types/core";
2
2
  import { makeStateUpdater } from "../../utils";
3
+ import { throwError } from "../../utilities/errors";
4
+
5
+ const promiseErrorMessage = "sync dataLoader returned promise";
3
6
 
4
7
  export const syncDataLoaderFeature: FeatureImplementation = {
5
8
  key: "sync-data-loader",
6
9
 
7
10
  getInitialState: (initialState) => ({
8
- loadingItems: [],
11
+ loadingItemData: [],
12
+ loadingItemChildrens: [],
9
13
  ...initialState,
10
14
  }),
11
15
 
12
16
  getDefaultConfig: (defaultConfig, tree) => ({
13
- setLoadingItems: makeStateUpdater("loadingItems", tree),
17
+ setLoadingItemData: makeStateUpdater("loadingItemData", tree),
18
+ setLoadingItemChildrens: makeStateUpdater("loadingItemChildrens", tree),
14
19
  ...defaultConfig,
15
20
  }),
16
21
 
17
22
  stateHandlerNames: {
18
- loadingItems: "setLoadingItems",
23
+ loadingItemData: "setLoadingItemData",
24
+ loadingItemChildrens: "setLoadingItemChildrens",
19
25
  },
20
26
 
21
27
  treeInstance: {
22
- retrieveItemData: ({ tree }, itemId) =>
23
- tree.getConfig().dataLoader!.getItem(itemId),
28
+ waitForItemDataLoaded: async () => {},
29
+ waitForItemChildrenLoaded: async () => {},
30
+
31
+ retrieveItemData: ({ tree }, itemId) => {
32
+ const data = tree.getConfig().dataLoader.getItem(itemId);
33
+ if (typeof data === "object" && "then" in data) {
34
+ throw throwError(promiseErrorMessage);
35
+ }
36
+ return data;
37
+ },
24
38
 
25
- retrieveChildrenIds: ({ tree }, itemId) =>
26
- tree.getConfig().dataLoader!.getChildren(itemId),
39
+ retrieveChildrenIds: ({ tree }, itemId) => {
40
+ const data = tree.getConfig().dataLoader.getChildren(itemId);
41
+ if (typeof data === "object" && "then" in data) {
42
+ throw throwError(promiseErrorMessage);
43
+ }
44
+ return data;
45
+ },
27
46
  },
28
47
 
29
48
  itemInstance: {
@@ -1,13 +1,13 @@
1
- export type SyncTreeDataLoader<T> = {
2
- getItem: (itemId: string) => T;
3
- getChildren: (itemId: string) => string[];
4
- };
1
+ export interface TreeDataLoader<T> {
2
+ getItem: (itemId: string) => T | Promise<T>;
3
+ getChildren: (itemId: string) => string[] | Promise<string[]>;
4
+ }
5
5
 
6
6
  export type SyncDataLoaderFeatureDef<T> = {
7
7
  state: {};
8
8
  config: {
9
9
  rootItemId: string;
10
- dataLoader?: SyncTreeDataLoader<T>;
10
+ dataLoader: TreeDataLoader<T>;
11
11
  };
12
12
  treeInstance: {
13
13
  retrieveItemData: (itemId: string) => T;
@@ -27,7 +27,7 @@ export const treeFeature: FeatureImplementation<any> = {
27
27
  const { rootItemId } = tree.getConfig();
28
28
  const { expandedItems } = tree.getState();
29
29
  const flatItems: ItemMeta[] = [];
30
- const expandedItemsSet = new Set(expandedItems);
30
+ const expandedItemsSet = new Set(expandedItems); // TODO support setting state expandedItems as set instead of array
31
31
 
32
32
  const recursiveAdd = (
33
33
  itemId: string,
@@ -46,7 +46,6 @@ export const treeFeature: FeatureImplementation<any> = {
46
46
  });
47
47
 
48
48
  if (expandedItemsSet.has(itemId)) {
49
- // TODO THIS MADE A HUGE DIFFERENCE!
50
49
  const children = tree.retrieveChildrenIds(itemId) ?? [];
51
50
  let i = 0;
52
51
  for (const childId of children) {
@@ -97,11 +96,11 @@ export const treeFeature: FeatureImplementation<any> = {
97
96
  },
98
97
 
99
98
  // TODO add label parameter
100
- getContainerProps: ({ prev, tree }) => ({
99
+ getContainerProps: ({ prev, tree }, treeLabel) => ({
101
100
  ...prev?.(),
102
101
  role: "tree",
103
- "aria-label": "",
104
- ref: tree.registerElement, // TODO memo
102
+ "aria-label": treeLabel ?? "",
103
+ ref: tree.registerElement,
105
104
  }),
106
105
 
107
106
  // relevant for hotkeys of this feature
@@ -119,7 +118,7 @@ export const treeFeature: FeatureImplementation<any> = {
119
118
  const itemMeta = item.getItemMeta();
120
119
  return {
121
120
  ...prev?.(),
122
- ref: item.registerElement, // TODO memo
121
+ ref: item.registerElement,
123
122
  role: "treeitem",
124
123
  "aria-setsize": itemMeta.setSize,
125
124
  "aria-posinset": itemMeta.posInSet,
@@ -152,7 +151,7 @@ export const treeFeature: FeatureImplementation<any> = {
152
151
  return;
153
152
  }
154
153
 
155
- if (tree.getState().loadingItems?.includes(itemId)) {
154
+ if (tree.getState().loadingItemChildrens?.includes(itemId)) {
156
155
  return;
157
156
  }
158
157
 
@@ -202,10 +201,8 @@ export const treeFeature: FeatureImplementation<any> = {
202
201
  ? tree.getItemInstance(item.getItemMeta().parentId)
203
202
  : undefined,
204
203
  getIndexInParent: ({ item }) => item.getItemMeta().posInSet,
205
- getChildren: ({ tree, item }) =>
206
- tree
207
- .retrieveChildrenIds(item.getItemMeta().itemId)
208
- .map((id) => tree.getItemInstance(id)),
204
+ getChildren: ({ tree, itemId }) =>
205
+ tree.retrieveChildrenIds(itemId).map((id) => tree.getItemInstance(id)),
209
206
  getTree: ({ tree }) => tree as any,
210
207
  getItemAbove: ({ tree, item }) =>
211
208
  tree.getItems()[item.getItemMeta().index - 1],
@@ -1,18 +1,18 @@
1
1
  import { ItemInstance, SetStateFn, TreeInstance } from "../../types/core";
2
2
 
3
- export type ItemMeta = {
3
+ export interface ItemMeta {
4
4
  itemId: string;
5
5
  parentId: string;
6
6
  level: number;
7
7
  index: number;
8
8
  setSize: number;
9
9
  posInSet: number;
10
- };
10
+ }
11
11
 
12
- export type TreeItemDataRef = {
12
+ export interface TreeItemDataRef {
13
13
  memoizedValues: Record<string, any>;
14
14
  memoizedDeps: Record<string, any[] | undefined>;
15
- };
15
+ }
16
16
 
17
17
  export type TreeFeatureDef<T> = {
18
18
  state: {
@@ -38,7 +38,9 @@ export type TreeFeatureDef<T> = {
38
38
  focusPreviousItem: () => void;
39
39
  updateDomFocus: () => void;
40
40
 
41
- getContainerProps: () => Record<string, any>;
41
+ /** Pass to the container rendering the tree children. The `treeLabel` parameter
42
+ * will be passed as `aria-label` parameter, and is recommended to be set. */
43
+ getContainerProps: (treeLabel?: string) => Record<string, any>;
42
44
  };
43
45
  itemInstance: {
44
46
  getId: () => string;
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export * from "./core/create-tree";
4
4
  export * from "./features/tree/types";
5
5
  export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
6
6
  export * from "./features/drag-and-drop/types";
7
+ export * from "./features/keyboard-drag-and-drop/types";
7
8
  export * from "./features/selection/types";
8
9
  export * from "./features/async-data-loader/types";
9
10
  export * from "./features/sync-data-loader/types";
@@ -18,6 +19,7 @@ export * from "./features/hotkeys-core/feature";
18
19
  export * from "./features/async-data-loader/feature";
19
20
  export * from "./features/sync-data-loader/feature";
20
21
  export * from "./features/drag-and-drop/feature";
22
+ export * from "./features/keyboard-drag-and-drop/feature";
21
23
  export * from "./features/search/feature";
22
24
  export * from "./features/renaming/feature";
23
25
  export * from "./features/expand-all/feature";
@@ -9,6 +9,7 @@ import { SelectionFeatureDef } from "./features/selection/types";
9
9
  import { SyncDataLoaderFeatureDef } from "./features/sync-data-loader/types";
10
10
  import { TreeFeatureDef } from "./features/tree/types";
11
11
  import { PropMemoizationFeatureDef } from "./features/prop-memoization/types";
12
+ import { KeyboardDragAndDropFeatureDef } from "./features/keyboard-drag-and-drop/types";
12
13
 
13
14
  export * from ".";
14
15
 
@@ -49,6 +50,21 @@ export type DragAndDropFeatureItemInstance<T> =
49
50
  DragAndDropFeatureDef<T>["itemInstance"];
50
51
  export type DragAndDropFeatureHotkeys<T> = DragAndDropFeatureDef<T>["hotkeys"];
51
52
 
53
+ /** @interface */
54
+ export type KeyboardDragAndDropFeatureConfig<T> =
55
+ KeyboardDragAndDropFeatureDef<T>["config"];
56
+ /** @interface */
57
+ export type KeyboardDragAndDropFeatureState<T> =
58
+ KeyboardDragAndDropFeatureDef<T>["state"];
59
+ /** @interface */
60
+ export type KeyboardDragAndDropFeatureTreeInstance<T> =
61
+ KeyboardDragAndDropFeatureDef<T>["treeInstance"];
62
+ /** @interface */
63
+ export type KeyboardDragAndDropFeatureItemInstance<T> =
64
+ KeyboardDragAndDropFeatureDef<T>["itemInstance"];
65
+ export type KeyboardDragAndDropFeatureHotkeys<T> =
66
+ KeyboardDragAndDropFeatureDef<T>["hotkeys"];
67
+
52
68
  /** @interface */
53
69
  export type ExpandAllFeatureConfig = ExpandAllFeatureDef["config"];
54
70
  /** @interface */
@@ -108,13 +108,13 @@ export class TestTreeDo<T> {
108
108
  return e;
109
109
  }
110
110
 
111
- drop(itemId: string, event?: DragEvent) {
111
+ async drop(itemId: string, event?: DragEvent) {
112
112
  const e = event ?? TestTree.dragEvent();
113
- this.itemProps(itemId).onDrop(e);
113
+ await this.itemProps(itemId).onDrop(e);
114
114
  return e;
115
115
  }
116
116
 
117
- dragOverAndDrop(itemId: string, event?: DragEvent) {
117
+ async dragOverAndDrop(itemId: string, event?: DragEvent) {
118
118
  const e = event ?? TestTree.dragEvent();
119
119
  this.dragOver(itemId, e);
120
120
  return this.drop(itemId, e);
@@ -2,7 +2,8 @@
2
2
  import { Mock, expect } from "vitest";
3
3
  import { DragEvent } from "react";
4
4
  import { TestTree } from "./test-tree";
5
- import { DropTarget } from "../features/drag-and-drop/types";
5
+ import { DragTarget } from "../features/drag-and-drop/types";
6
+ import { TreeState } from "../types/core";
6
7
 
7
8
  export class TestTreeExpect<T> {
8
9
  protected itemInstance(itemId: string) {
@@ -39,7 +40,11 @@ export class TestTreeExpect<T> {
39
40
  expect(itemChildren).toEqual(children);
40
41
  }
41
42
 
42
- dropped(draggedItems: string[], target: DropTarget<any>) {
43
+ substate<K extends keyof TreeState<T>>(key: K, value: TreeState<T>[K]) {
44
+ expect(this.tree.instance.getState()[key]).toEqual(value);
45
+ }
46
+
47
+ dropped(draggedItems: string[], target: DragTarget<any>) {
43
48
  expect(this.tree.instance.getConfig().onDrop).toBeCalledWith(
44
49
  draggedItems.map((id) => this.tree.item(id)),
45
50
  target,
@@ -8,6 +8,7 @@ import { TestTreeExpect } from "./test-tree-expect";
8
8
  import { syncDataLoaderFeature } from "../features/sync-data-loader/feature";
9
9
  import { asyncDataLoaderFeature } from "../features/async-data-loader/feature";
10
10
  import { buildProxiedInstance } from "../core/build-proxified-instance";
11
+ import { TreeDataLoader } from "../features/sync-data-loader/types";
11
12
 
12
13
  vi.useFakeTimers({ shouldAdvanceTime: true });
13
14
 
@@ -20,13 +21,32 @@ export class TestTree<T = string> {
20
21
 
21
22
  private static asyncLoaderResolvers: (() => void)[] = [];
22
23
 
24
+ private asyncDataLoaderImp: TreeDataLoader<T> = {
25
+ getItem: async (id: string) => {
26
+ await new Promise<void>((r) => {
27
+ (r as any).debugName = `Loading getItem ${id}`;
28
+ TestTree.asyncLoaderResolvers.push(r);
29
+ });
30
+ return id as T;
31
+ },
32
+ getChildren: async (id: string) => {
33
+ await new Promise<void>((r) => {
34
+ (r as any).debugName = `Loading getChildren ${id}`;
35
+ TestTree.asyncLoaderResolvers.push(r);
36
+ });
37
+ return [`${id}1`, `${id}2`, `${id}3`, `${id}4`];
38
+ },
39
+ };
40
+
23
41
  suits = {
24
42
  sync: () => ({
25
43
  tree: this.withFeatures(syncDataLoaderFeature),
26
44
  title: "Synchronous Data Loader",
27
45
  }),
28
46
  async: () => ({
29
- tree: this.withFeatures(asyncDataLoaderFeature),
47
+ tree: this.withFeatures(asyncDataLoaderFeature).with({
48
+ dataLoader: this.asyncDataLoaderImp,
49
+ }),
30
50
  title: "Asynchronous Data Loader",
31
51
  }),
32
52
  proxifiedSync: () => ({
@@ -65,12 +85,12 @@ export class TestTree<T = string> {
65
85
  private constructor(private config: TreeConfig<T>) {}
66
86
 
67
87
  static async resolveAsyncLoaders() {
68
- while (TestTree.asyncLoaderResolvers.length) {
88
+ do {
69
89
  TestTree.asyncLoaderResolvers.shift()?.();
70
90
  await new Promise<void>((r) => {
71
91
  setTimeout(r);
72
92
  });
73
- }
93
+ } while (TestTree.asyncLoaderResolvers.length);
74
94
  }
75
95
 
76
96
  async resolveAsyncVisibleItems() {
@@ -88,22 +108,6 @@ export class TestTree<T = string> {
88
108
  getItem: (id) => id,
89
109
  getChildren: (id) => [`${id}1`, `${id}2`, `${id}3`, `${id}4`],
90
110
  },
91
- asyncDataLoader: {
92
- getItem: async (id) => {
93
- await new Promise<void>((r) => {
94
- (r as any).debugName = `Loading getItem ${id}`;
95
- TestTree.asyncLoaderResolvers.push(r);
96
- });
97
- return id;
98
- },
99
- getChildren: async (id) => {
100
- await new Promise<void>((r) => {
101
- (r as any).debugName = `Loading getChildren ${id}`;
102
- TestTree.asyncLoaderResolvers.push(r);
103
- });
104
- return [`${id}1`, `${id}2`, `${id}3`, `${id}4`];
105
- },
106
- },
107
111
  getItemName: (item) => item.getItemData(),
108
112
  indent: 20,
109
113
  isItemFolder: (item) => item.getItemMeta().level < 2,
@@ -203,7 +207,7 @@ export class TestTree<T = string> {
203
207
  } as HTMLElement);
204
208
  }
205
209
 
206
- static dragEvent(pageX = 1000, pageY = 0) {
210
+ static dragEvent(clientX = 1000, clientY = 0) {
207
211
  return {
208
212
  preventDefault: vi.fn(),
209
213
  stopPropagation: vi.fn(),
@@ -212,8 +216,8 @@ export class TestTree<T = string> {
212
216
  getData: vi.fn(),
213
217
  dropEffect: "unchaged-from-test",
214
218
  },
215
- pageX,
216
- pageY,
219
+ clientX,
220
+ clientY,
217
221
  } as unknown as DragEvent;
218
222
  }
219
223
 
package/src/types/core.ts CHANGED
@@ -12,6 +12,7 @@ import { SearchFeatureDef } from "../features/search/types";
12
12
  import { RenamingFeatureDef } from "../features/renaming/types";
13
13
  import { ExpandAllFeatureDef } from "../features/expand-all/types";
14
14
  import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
15
+ import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
15
16
 
16
17
  export type Updater<T> = T | ((old: T) => T);
17
18
  export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
@@ -53,6 +54,7 @@ export type RegisteredFeatures<T> =
53
54
  | TreeFeatureDef<T>
54
55
  | SelectionFeatureDef<T>
55
56
  | DragAndDropFeatureDef<T>
57
+ | KeyboardDragAndDropFeatureDef<T>
56
58
  | HotkeysCoreFeatureDef<T>
57
59
  | SyncDataLoaderFeatureDef<T>
58
60
  | AsyncDataLoaderFeatureDef<T>
@@ -1,5 +1,5 @@
1
1
  import { ItemInstance } from "../types/core";
2
- import { DropTarget } from "../features/drag-and-drop/types";
2
+ import { DragTarget } from "../features/drag-and-drop/types";
3
3
  import { removeItemsFromParents } from "./remove-items-from-parents";
4
4
  import { insertItemsAtTarget } from "./insert-items-at-target";
5
5
 
@@ -7,8 +7,8 @@ export const createOnDropHandler =
7
7
  <T>(
8
8
  onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void,
9
9
  ) =>
10
- (items: ItemInstance<T>[], target: DropTarget<T>) => {
10
+ async (items: ItemInstance<T>[], target: DragTarget<T>) => {
11
11
  const itemIds = items.map((item) => item.getId());
12
- removeItemsFromParents(items, onChangeChildren);
13
- insertItemsAtTarget(itemIds, target, onChangeChildren);
12
+ await removeItemsFromParents(items, onChangeChildren);
13
+ await insertItemsAtTarget(itemIds, target, onChangeChildren);
14
14
  };
@@ -1,18 +1,23 @@
1
1
  import { ItemInstance } from "../types/core";
2
- import { DropTarget } from "../features/drag-and-drop/types";
2
+ import { DragTarget } from "../features/drag-and-drop/types";
3
3
 
4
- export const insertItemsAtTarget = <T>(
4
+ export const insertItemsAtTarget = async <T>(
5
5
  itemIds: string[],
6
- target: DropTarget<T>,
7
- onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void,
6
+ target: DragTarget<T>,
7
+ onChangeChildren: (
8
+ item: ItemInstance<T>,
9
+ newChildrenIds: string[],
10
+ ) => Promise<void> | void,
8
11
  ) => {
12
+ await target.item.getTree().waitForItemChildrenLoaded(target.item.getId());
13
+ const oldChildrenIds = target.item
14
+ .getTree()
15
+ .retrieveChildrenIds(target.item.getId());
16
+
9
17
  // add moved items to new common parent, if dropped onto parent
10
- if (target.childIndex === null) {
11
- const newChildren = [
12
- ...target.item.getChildren().map((item) => item.getId()),
13
- ...itemIds,
14
- ];
15
- onChangeChildren(target.item, newChildren);
18
+ if (!("childIndex" in target)) {
19
+ const newChildren = [...oldChildrenIds, ...itemIds];
20
+ await onChangeChildren(target.item, newChildren);
16
21
  if (target.item && "updateCachedChildrenIds" in target.item) {
17
22
  target.item.updateCachedChildrenIds(newChildren);
18
23
  }
@@ -21,14 +26,13 @@ export const insertItemsAtTarget = <T>(
21
26
  }
22
27
 
23
28
  // add moved items to new common parent, if dropped between siblings
24
- const oldChildren = target.item.getChildren();
25
29
  const newChildren = [
26
- ...oldChildren.slice(0, target.insertionIndex).map((item) => item.getId()),
30
+ ...oldChildrenIds.slice(0, target.insertionIndex),
27
31
  ...itemIds,
28
- ...oldChildren.slice(target.insertionIndex).map((item) => item.getId()),
32
+ ...oldChildrenIds.slice(target.insertionIndex),
29
33
  ];
30
34
 
31
- onChangeChildren(target.item, newChildren);
35
+ await onChangeChildren(target.item, newChildren);
32
36
 
33
37
  if (target.item && "updateCachedChildrenIds" in target.item) {
34
38
  target.item.updateCachedChildrenIds(newChildren);
@@ -1,8 +1,11 @@
1
1
  import { ItemInstance } from "../types/core";
2
2
 
3
- export const removeItemsFromParents = <T>(
3
+ export const removeItemsFromParents = async <T>(
4
4
  movedItems: ItemInstance<T>[],
5
- onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void,
5
+ onChangeChildren: (
6
+ item: ItemInstance<T>,
7
+ newChildrenIds: string[],
8
+ ) => void | Promise<void>,
6
9
  ) => {
7
10
  const movedItemsIds = movedItems.map((item) => item.getId());
8
11
  const uniqueParents = [
@@ -15,7 +18,7 @@ export const removeItemsFromParents = <T>(
15
18
  const newChildren = siblings
16
19
  .filter((sibling) => !movedItemsIds.includes(sibling.getId()))
17
20
  .map((i) => i.getId());
18
- onChangeChildren(parent, newChildren);
21
+ await onChangeChildren(parent, newChildren);
19
22
  if (parent && "updateCachedChildrenIds" in parent) {
20
23
  parent?.updateCachedChildrenIds(newChildren);
21
24
  }