@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
@@ -1,14 +1,40 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import { makeStateUpdater } from "../../utils";
11
+ import { throwError } from "../../utilities/errors";
12
+ const promiseErrorMessage = "sync dataLoader returned promise";
2
13
  export const syncDataLoaderFeature = {
3
14
  key: "sync-data-loader",
4
- getInitialState: (initialState) => (Object.assign({ loadingItems: [] }, initialState)),
5
- getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setLoadingItems: makeStateUpdater("loadingItems", tree) }, defaultConfig)),
15
+ getInitialState: (initialState) => (Object.assign({ loadingItemData: [], loadingItemChildrens: [] }, initialState)),
16
+ getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setLoadingItemData: makeStateUpdater("loadingItemData", tree), setLoadingItemChildrens: makeStateUpdater("loadingItemChildrens", tree) }, defaultConfig)),
6
17
  stateHandlerNames: {
7
- loadingItems: "setLoadingItems",
18
+ loadingItemData: "setLoadingItemData",
19
+ loadingItemChildrens: "setLoadingItemChildrens",
8
20
  },
9
21
  treeInstance: {
10
- retrieveItemData: ({ tree }, itemId) => tree.getConfig().dataLoader.getItem(itemId),
11
- retrieveChildrenIds: ({ tree }, itemId) => tree.getConfig().dataLoader.getChildren(itemId),
22
+ waitForItemDataLoaded: () => __awaiter(void 0, void 0, void 0, function* () { }),
23
+ waitForItemChildrenLoaded: () => __awaiter(void 0, void 0, void 0, function* () { }),
24
+ retrieveItemData: ({ tree }, itemId) => {
25
+ const data = tree.getConfig().dataLoader.getItem(itemId);
26
+ if (typeof data === "object" && "then" in data) {
27
+ throw throwError(promiseErrorMessage);
28
+ }
29
+ return data;
30
+ },
31
+ retrieveChildrenIds: ({ tree }, itemId) => {
32
+ const data = tree.getConfig().dataLoader.getChildren(itemId);
33
+ if (typeof data === "object" && "then" in data) {
34
+ throw throwError(promiseErrorMessage);
35
+ }
36
+ return data;
37
+ },
12
38
  },
13
39
  itemInstance: {
14
40
  isLoading: () => false,
@@ -1,12 +1,12 @@
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
  export type SyncDataLoaderFeatureDef<T> = {
6
6
  state: {};
7
7
  config: {
8
8
  rootItemId: string;
9
- dataLoader?: SyncTreeDataLoader<T>;
9
+ dataLoader: TreeDataLoader<T>;
10
10
  };
11
11
  treeInstance: {
12
12
  retrieveItemData: (itemId: string) => T;
@@ -21,7 +21,7 @@ export const treeFeature = {
21
21
  const { rootItemId } = tree.getConfig();
22
22
  const { expandedItems } = tree.getState();
23
23
  const flatItems = [];
24
- const expandedItemsSet = new Set(expandedItems);
24
+ const expandedItemsSet = new Set(expandedItems); // TODO support setting state expandedItems as set instead of array
25
25
  const recursiveAdd = (itemId, parentId, level, setSize, posInSet) => {
26
26
  var _a;
27
27
  flatItems.push({
@@ -33,7 +33,6 @@ export const treeFeature = {
33
33
  posInSet,
34
34
  });
35
35
  if (expandedItemsSet.has(itemId)) {
36
- // TODO THIS MADE A HUGE DIFFERENCE!
37
36
  const children = (_a = tree.retrieveChildrenIds(itemId)) !== null && _a !== void 0 ? _a : [];
38
37
  let i = 0;
39
38
  for (const childId of children) {
@@ -79,7 +78,7 @@ export const treeFeature = {
79
78
  }));
80
79
  },
81
80
  // TODO add label parameter
82
- getContainerProps: ({ prev, tree }) => (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { role: "tree", "aria-label": "", ref: tree.registerElement })),
81
+ getContainerProps: ({ prev, tree }, treeLabel) => (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { role: "tree", "aria-label": treeLabel !== null && treeLabel !== void 0 ? treeLabel : "", ref: tree.registerElement })),
83
82
  // relevant for hotkeys of this feature
84
83
  isSearchOpen: () => false,
85
84
  },
@@ -115,7 +114,7 @@ export const treeFeature = {
115
114
  if (!item.isFolder()) {
116
115
  return;
117
116
  }
118
- if ((_a = tree.getState().loadingItems) === null || _a === void 0 ? void 0 : _a.includes(itemId)) {
117
+ if ((_a = tree.getState().loadingItemChildrens) === null || _a === void 0 ? void 0 : _a.includes(itemId)) {
119
118
  return;
120
119
  }
121
120
  tree.applySubStateUpdate("expandedItems", (expandedItems) => [
@@ -154,9 +153,7 @@ export const treeFeature = {
154
153
  ? tree.getItemInstance(item.getItemMeta().parentId)
155
154
  : undefined,
156
155
  getIndexInParent: ({ item }) => item.getItemMeta().posInSet,
157
- getChildren: ({ tree, item }) => tree
158
- .retrieveChildrenIds(item.getItemMeta().itemId)
159
- .map((id) => tree.getItemInstance(id)),
156
+ getChildren: ({ tree, itemId }) => tree.retrieveChildrenIds(itemId).map((id) => tree.getItemInstance(id)),
160
157
  getTree: ({ tree }) => tree,
161
158
  getItemAbove: ({ tree, item }) => tree.getItems()[item.getItemMeta().index - 1],
162
159
  getItemBelow: ({ tree, item }) => tree.getItems()[item.getItemMeta().index + 1],
@@ -1,16 +1,16 @@
1
1
  import { ItemInstance, SetStateFn, TreeInstance } from "../../types/core";
2
- export type ItemMeta = {
2
+ export interface ItemMeta {
3
3
  itemId: string;
4
4
  parentId: string;
5
5
  level: number;
6
6
  index: number;
7
7
  setSize: number;
8
8
  posInSet: number;
9
- };
10
- export type TreeItemDataRef = {
9
+ }
10
+ export interface TreeItemDataRef {
11
11
  memoizedValues: Record<string, any>;
12
12
  memoizedDeps: Record<string, any[] | undefined>;
13
- };
13
+ }
14
14
  export type TreeFeatureDef<T> = {
15
15
  state: {
16
16
  expandedItems: string[];
@@ -31,7 +31,9 @@ export type TreeFeatureDef<T> = {
31
31
  focusNextItem: () => void;
32
32
  focusPreviousItem: () => void;
33
33
  updateDomFocus: () => void;
34
- getContainerProps: () => Record<string, any>;
34
+ /** Pass to the container rendering the tree children. The `treeLabel` parameter
35
+ * will be passed as `aria-label` parameter, and is recommended to be set. */
36
+ getContainerProps: (treeLabel?: string) => Record<string, any>;
35
37
  };
36
38
  itemInstance: {
37
39
  getId: () => string;
@@ -3,6 +3,7 @@ export * from "./core/create-tree";
3
3
  export * from "./features/tree/types";
4
4
  export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
5
5
  export * from "./features/drag-and-drop/types";
6
+ export * from "./features/keyboard-drag-and-drop/types";
6
7
  export * from "./features/selection/types";
7
8
  export * from "./features/async-data-loader/types";
8
9
  export * from "./features/sync-data-loader/types";
@@ -16,6 +17,7 @@ export * from "./features/hotkeys-core/feature";
16
17
  export * from "./features/async-data-loader/feature";
17
18
  export * from "./features/sync-data-loader/feature";
18
19
  export * from "./features/drag-and-drop/feature";
20
+ export * from "./features/keyboard-drag-and-drop/feature";
19
21
  export * from "./features/search/feature";
20
22
  export * from "./features/renaming/feature";
21
23
  export * from "./features/expand-all/feature";
package/lib/esm/index.js CHANGED
@@ -2,6 +2,7 @@ export * from "./types/core";
2
2
  export * from "./core/create-tree";
3
3
  export * from "./features/tree/types";
4
4
  export * from "./features/drag-and-drop/types";
5
+ export * from "./features/keyboard-drag-and-drop/types";
5
6
  export * from "./features/selection/types";
6
7
  export * from "./features/async-data-loader/types";
7
8
  export * from "./features/sync-data-loader/types";
@@ -15,6 +16,7 @@ export * from "./features/hotkeys-core/feature";
15
16
  export * from "./features/async-data-loader/feature";
16
17
  export * from "./features/sync-data-loader/feature";
17
18
  export * from "./features/drag-and-drop/feature";
19
+ export * from "./features/keyboard-drag-and-drop/feature";
18
20
  export * from "./features/search/feature";
19
21
  export * from "./features/renaming/feature";
20
22
  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
  export * from ".";
13
14
  /** @interface */
14
15
  export type AsyncDataLoaderFeatureConfig<T> = AsyncDataLoaderFeatureDef<T>["config"];
@@ -29,6 +30,15 @@ export type DragAndDropFeatureTreeInstance<T> = DragAndDropFeatureDef<T>["treeIn
29
30
  export type DragAndDropFeatureItemInstance<T> = DragAndDropFeatureDef<T>["itemInstance"];
30
31
  export type DragAndDropFeatureHotkeys<T> = DragAndDropFeatureDef<T>["hotkeys"];
31
32
  /** @interface */
33
+ export type KeyboardDragAndDropFeatureConfig<T> = KeyboardDragAndDropFeatureDef<T>["config"];
34
+ /** @interface */
35
+ export type KeyboardDragAndDropFeatureState<T> = KeyboardDragAndDropFeatureDef<T>["state"];
36
+ /** @interface */
37
+ export type KeyboardDragAndDropFeatureTreeInstance<T> = KeyboardDragAndDropFeatureDef<T>["treeInstance"];
38
+ /** @interface */
39
+ export type KeyboardDragAndDropFeatureItemInstance<T> = KeyboardDragAndDropFeatureDef<T>["itemInstance"];
40
+ export type KeyboardDragAndDropFeatureHotkeys<T> = KeyboardDragAndDropFeatureDef<T>["hotkeys"];
41
+ /** @interface */
32
42
  export type ExpandAllFeatureConfig = ExpandAllFeatureDef["config"];
33
43
  /** @interface */
34
44
  export type ExpandAllFeatureState = ExpandAllFeatureDef["state"];
@@ -17,7 +17,7 @@ export declare class TestTreeDo<T> {
17
17
  dragOverNotAllowed(itemId: string, event?: DragEvent): DragEvent<Element>;
18
18
  dragLeave(itemId: string): void;
19
19
  dragEnd(itemId: string, event?: DragEvent): DragEvent<Element>;
20
- drop(itemId: string, event?: DragEvent): DragEvent<Element>;
21
- dragOverAndDrop(itemId: string, event?: DragEvent): DragEvent<Element>;
20
+ drop(itemId: string, event?: DragEvent): Promise<DragEvent<Element>>;
21
+ dragOverAndDrop(itemId: string, event?: DragEvent): Promise<DragEvent<Element>>;
22
22
  private consistentCalls;
23
23
  }
@@ -1,3 +1,12 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import { expect, vi } from "vitest";
2
11
  import { TestTree } from "./test-tree";
3
12
  export class TestTreeDo {
@@ -76,14 +85,18 @@ export class TestTreeDo {
76
85
  return e;
77
86
  }
78
87
  drop(itemId, event) {
79
- const e = event !== null && event !== void 0 ? event : TestTree.dragEvent();
80
- this.itemProps(itemId).onDrop(e);
81
- return e;
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ const e = event !== null && event !== void 0 ? event : TestTree.dragEvent();
90
+ yield this.itemProps(itemId).onDrop(e);
91
+ return e;
92
+ });
82
93
  }
83
94
  dragOverAndDrop(itemId, event) {
84
- const e = event !== null && event !== void 0 ? event : TestTree.dragEvent();
85
- this.dragOver(itemId, e);
86
- return this.drop(itemId, e);
95
+ return __awaiter(this, void 0, void 0, function* () {
96
+ const e = event !== null && event !== void 0 ? event : TestTree.dragEvent();
97
+ this.dragOver(itemId, e);
98
+ return this.drop(itemId, e);
99
+ });
87
100
  }
88
101
  consistentCalls(fn) {
89
102
  if (!vi.isMockFunction(fn)) {
@@ -1,15 +1,17 @@
1
1
  import { DragEvent } from "react";
2
2
  import { TestTree } from "./test-tree";
3
- import { DropTarget } from "../features/drag-and-drop/types";
3
+ import { DragTarget } from "../features/drag-and-drop/types";
4
+ import { TreeState } from "../types/core";
4
5
  export declare class TestTreeExpect<T> {
5
6
  private tree;
6
- protected itemInstance(itemId: string): import("..").ItemInstance<T>;
7
+ protected itemInstance(itemId: string): import("../types/core").ItemInstance<T>;
7
8
  protected itemProps(itemId: string): Record<string, any>;
8
9
  constructor(tree: TestTree<T>);
9
10
  foldersExpanded(...itemIds: string[]): void;
10
11
  foldersCollapsed(...itemIds: string[]): void;
11
12
  hasChildren(itemId: string, children: string[]): void;
12
- dropped(draggedItems: string[], target: DropTarget<any>): void;
13
+ substate<K extends keyof TreeState<T>>(key: K, value: TreeState<T>[K]): void;
14
+ dropped(draggedItems: string[], target: DragTarget<any>): void;
13
15
  dragOverNotAllowed(itemId: string, event?: DragEvent): DragEvent<Element>;
14
16
  defaultDragLineProps(indent?: number): void;
15
17
  }
@@ -26,6 +26,9 @@ export class TestTreeExpect {
26
26
  const itemChildren = item.getChildren().map((child) => child.getId());
27
27
  expect(itemChildren).toEqual(children);
28
28
  }
29
+ substate(key, value) {
30
+ expect(this.tree.instance.getState()[key]).toEqual(value);
31
+ }
29
32
  dropped(draggedItems, target) {
30
33
  expect(this.tree.instance.getConfig().onDrop).toBeCalledWith(draggedItems.map((id) => this.tree.item(id)), target);
31
34
  }
@@ -8,6 +8,7 @@ export declare class TestTree<T = string> {
8
8
  readonly expect: TestTreeExpect<T>;
9
9
  private treeInstance;
10
10
  private static asyncLoaderResolvers;
11
+ private asyncDataLoaderImp;
11
12
  suits: {
12
13
  sync: () => {
13
14
  tree: TestTree<T>;
@@ -41,7 +42,7 @@ export declare class TestTree<T = string> {
41
42
  reset(): void;
42
43
  debug(): void;
43
44
  setElementBoundingBox(itemId: string, bb?: Partial<DOMRect>): void;
44
- static dragEvent(pageX?: number, pageY?: number): DragEvent;
45
+ static dragEvent(clientX?: number, clientY?: number): DragEvent;
45
46
  createTopDragEvent(indent?: number): DragEvent<Element>;
46
47
  createBottomDragEvent(indent?: number): DragEvent<Element>;
47
48
  }
@@ -39,13 +39,31 @@ export class TestTree {
39
39
  this.do = new TestTreeDo(this);
40
40
  this.expect = new TestTreeExpect(this);
41
41
  this.treeInstance = null;
42
+ this.asyncDataLoaderImp = {
43
+ getItem: (id) => __awaiter(this, void 0, void 0, function* () {
44
+ yield new Promise((r) => {
45
+ r.debugName = `Loading getItem ${id}`;
46
+ TestTree.asyncLoaderResolvers.push(r);
47
+ });
48
+ return id;
49
+ }),
50
+ getChildren: (id) => __awaiter(this, void 0, void 0, function* () {
51
+ yield new Promise((r) => {
52
+ r.debugName = `Loading getChildren ${id}`;
53
+ TestTree.asyncLoaderResolvers.push(r);
54
+ });
55
+ return [`${id}1`, `${id}2`, `${id}3`, `${id}4`];
56
+ }),
57
+ };
42
58
  this.suits = {
43
59
  sync: () => ({
44
60
  tree: this.withFeatures(syncDataLoaderFeature),
45
61
  title: "Synchronous Data Loader",
46
62
  }),
47
63
  async: () => ({
48
- tree: this.withFeatures(asyncDataLoaderFeature),
64
+ tree: this.withFeatures(asyncDataLoaderFeature).with({
65
+ dataLoader: this.asyncDataLoaderImp,
66
+ }),
49
67
  title: "Asynchronous Data Loader",
50
68
  }),
51
69
  proxifiedSync: () => ({
@@ -65,12 +83,12 @@ export class TestTree {
65
83
  static resolveAsyncLoaders() {
66
84
  return __awaiter(this, void 0, void 0, function* () {
67
85
  var _a;
68
- while (TestTree.asyncLoaderResolvers.length) {
86
+ do {
69
87
  (_a = TestTree.asyncLoaderResolvers.shift()) === null || _a === void 0 ? void 0 : _a();
70
88
  yield new Promise((r) => {
71
89
  setTimeout(r);
72
90
  });
73
- }
91
+ } while (TestTree.asyncLoaderResolvers.length);
74
92
  });
75
93
  }
76
94
  resolveAsyncVisibleItems() {
@@ -85,21 +103,6 @@ export class TestTree {
85
103
  return new TestTree(Object.assign({ rootItemId: "x", createLoadingItemData: () => "loading", dataLoader: {
86
104
  getItem: (id) => id,
87
105
  getChildren: (id) => [`${id}1`, `${id}2`, `${id}3`, `${id}4`],
88
- }, asyncDataLoader: {
89
- getItem: (id) => __awaiter(this, void 0, void 0, function* () {
90
- yield new Promise((r) => {
91
- r.debugName = `Loading getItem ${id}`;
92
- TestTree.asyncLoaderResolvers.push(r);
93
- });
94
- return id;
95
- }),
96
- getChildren: (id) => __awaiter(this, void 0, void 0, function* () {
97
- yield new Promise((r) => {
98
- r.debugName = `Loading getChildren ${id}`;
99
- TestTree.asyncLoaderResolvers.push(r);
100
- });
101
- return [`${id}1`, `${id}2`, `${id}3`, `${id}4`];
102
- }),
103
106
  }, getItemName: (item) => item.getItemData(), indent: 20, isItemFolder: (item) => item.getItemMeta().level < 2, initialState: {
104
107
  expandedItems: ["x1", "x11"],
105
108
  }, features: [] }, config));
@@ -176,7 +179,7 @@ export class TestTree {
176
179
  getBoundingClientRect: () => bb,
177
180
  });
178
181
  }
179
- static dragEvent(pageX = 1000, pageY = 0) {
182
+ static dragEvent(clientX = 1000, clientY = 0) {
180
183
  return {
181
184
  preventDefault: vi.fn(),
182
185
  stopPropagation: vi.fn(),
@@ -185,8 +188,8 @@ export class TestTree {
185
188
  getData: vi.fn(),
186
189
  dropEffect: "unchaged-from-test",
187
190
  },
188
- pageX,
189
- pageY,
191
+ clientX,
192
+ clientY,
190
193
  };
191
194
  }
192
195
  createTopDragEvent(indent = 0) {
@@ -9,6 +9,7 @@ import { SearchFeatureDef } from "../features/search/types";
9
9
  import { RenamingFeatureDef } from "../features/renaming/types";
10
10
  import { ExpandAllFeatureDef } from "../features/expand-all/types";
11
11
  import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
12
+ import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
12
13
  export type Updater<T> = T | ((old: T) => T);
13
14
  export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
14
15
  export type FeatureDef = {
@@ -33,7 +34,7 @@ type MergedFeatures<F extends FeatureDef> = {
33
34
  itemInstance: UnionToIntersection<F["itemInstance"]>;
34
35
  hotkeys: F["hotkeys"];
35
36
  };
36
- export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | DragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
37
+ export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
37
38
  type TreeStateType<T> = MergedFeatures<RegisteredFeatures<T>>["state"];
38
39
  export interface TreeState<T> extends TreeStateType<T> {
39
40
  }
@@ -1,3 +1,3 @@
1
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;
2
+ import { DragTarget } from "../features/drag-and-drop/types";
3
+ export declare const createOnDropHandler: <T>(onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void) => (items: ItemInstance<T>[], target: DragTarget<T>) => Promise<void>;
@@ -1,7 +1,16 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import { removeItemsFromParents } from "./remove-items-from-parents";
2
11
  import { insertItemsAtTarget } from "./insert-items-at-target";
3
- export const createOnDropHandler = (onChangeChildren) => (items, target) => {
12
+ export const createOnDropHandler = (onChangeChildren) => (items, target) => __awaiter(void 0, void 0, void 0, function* () {
4
13
  const itemIds = items.map((item) => item.getId());
5
- removeItemsFromParents(items, onChangeChildren);
6
- insertItemsAtTarget(itemIds, target, onChangeChildren);
7
- };
14
+ yield removeItemsFromParents(items, onChangeChildren);
15
+ yield insertItemsAtTarget(itemIds, target, onChangeChildren);
16
+ });
@@ -1,3 +1,3 @@
1
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;
2
+ import { DragTarget } from "../features/drag-and-drop/types";
3
+ export declare const insertItemsAtTarget: <T>(itemIds: string[], target: DragTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => Promise<void> | void) => Promise<void>;
@@ -1,11 +1,21 @@
1
- export const insertItemsAtTarget = (itemIds, target, onChangeChildren) => {
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ export const insertItemsAtTarget = (itemIds, target, onChangeChildren) => __awaiter(void 0, void 0, void 0, function* () {
11
+ yield target.item.getTree().waitForItemChildrenLoaded(target.item.getId());
12
+ const oldChildrenIds = target.item
13
+ .getTree()
14
+ .retrieveChildrenIds(target.item.getId());
2
15
  // add moved items to new common parent, if dropped onto parent
3
- if (target.childIndex === null) {
4
- const newChildren = [
5
- ...target.item.getChildren().map((item) => item.getId()),
6
- ...itemIds,
7
- ];
8
- onChangeChildren(target.item, newChildren);
16
+ if (!("childIndex" in target)) {
17
+ const newChildren = [...oldChildrenIds, ...itemIds];
18
+ yield onChangeChildren(target.item, newChildren);
9
19
  if (target.item && "updateCachedChildrenIds" in target.item) {
10
20
  target.item.updateCachedChildrenIds(newChildren);
11
21
  }
@@ -13,15 +23,14 @@ export const insertItemsAtTarget = (itemIds, target, onChangeChildren) => {
13
23
  return;
14
24
  }
15
25
  // add moved items to new common parent, if dropped between siblings
16
- const oldChildren = target.item.getChildren();
17
26
  const newChildren = [
18
- ...oldChildren.slice(0, target.insertionIndex).map((item) => item.getId()),
27
+ ...oldChildrenIds.slice(0, target.insertionIndex),
19
28
  ...itemIds,
20
- ...oldChildren.slice(target.insertionIndex).map((item) => item.getId()),
29
+ ...oldChildrenIds.slice(target.insertionIndex),
21
30
  ];
22
- onChangeChildren(target.item, newChildren);
31
+ yield onChangeChildren(target.item, newChildren);
23
32
  if (target.item && "updateCachedChildrenIds" in target.item) {
24
33
  target.item.updateCachedChildrenIds(newChildren);
25
34
  }
26
35
  target.item.getTree().rebuildTree();
27
- };
36
+ });
@@ -1,2 +1,2 @@
1
1
  import { ItemInstance } from "../types/core";
2
- export declare const removeItemsFromParents: <T>(movedItems: ItemInstance<T>[], onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void) => void;
2
+ export declare const removeItemsFromParents: <T>(movedItems: ItemInstance<T>[], onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void | Promise<void>) => Promise<void>;
@@ -1,4 +1,13 @@
1
- export const removeItemsFromParents = (movedItems, onChangeChildren) => {
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ export const removeItemsFromParents = (movedItems, onChangeChildren) => __awaiter(void 0, void 0, void 0, function* () {
2
11
  const movedItemsIds = movedItems.map((item) => item.getId());
3
12
  const uniqueParents = [
4
13
  ...new Set(movedItems.map((item) => item.getParent())),
@@ -9,11 +18,11 @@ export const removeItemsFromParents = (movedItems, onChangeChildren) => {
9
18
  const newChildren = siblings
10
19
  .filter((sibling) => !movedItemsIds.includes(sibling.getId()))
11
20
  .map((i) => i.getId());
12
- onChangeChildren(parent, newChildren);
21
+ yield onChangeChildren(parent, newChildren);
13
22
  if (parent && "updateCachedChildrenIds" in parent) {
14
23
  parent === null || parent === void 0 ? void 0 : parent.updateCachedChildrenIds(newChildren);
15
24
  }
16
25
  }
17
26
  }
18
27
  movedItems[0].getTree().rebuildTree();
19
- };
28
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-tree/core",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "type": "module",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/esm/index.js",
@@ -21,7 +21,7 @@
21
21
  "license": "MIT",
22
22
  "devDependencies": {
23
23
  "jsdom": "^26.0.0",
24
- "typescript": "^5.7.3",
24
+ "typescript": "^5.7.2",
25
25
  "vitest": "^3.0.3"
26
26
  }
27
27
  }
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import { TestTree } from "../test-utils/test-tree";
3
+ import { buildStaticInstance } from "./build-static-instance";
3
4
 
4
5
  declare module "../types/core" {
5
6
  export interface TreeInstance<T> {
@@ -17,6 +18,36 @@ const factory = TestTree.default({});
17
18
 
18
19
  describe("core-feature/prop-memoization", () => {
19
20
  factory.forSuits((tree) => {
21
+ describe("rebuilds item instance", () => {
22
+ it("rebuilds when explicitly invoked", async () => {
23
+ const instanceBuilder = vi.fn().mockImplementation(buildStaticInstance);
24
+ const testTree = await tree
25
+ .with({ instanceBuilder })
26
+ .createTestCaseTree();
27
+ expect(instanceBuilder).toBeCalled();
28
+ instanceBuilder.mockClear();
29
+ testTree.instance.rebuildTree();
30
+ // if tree structure doesnt mutate, only root tree item is rebuilt actually
31
+ expect(instanceBuilder).toBeCalled();
32
+ });
33
+
34
+ it("rebuilds when config changes with new expanded items", async () => {
35
+ const instanceBuilder = vi.fn().mockImplementation(buildStaticInstance);
36
+ const testTree = await tree
37
+ .with({ instanceBuilder })
38
+ .createTestCaseTree();
39
+ expect(instanceBuilder).toBeCalled();
40
+ instanceBuilder.mockClear();
41
+ testTree.instance.setConfig((oldCfg) => ({
42
+ ...oldCfg,
43
+ state: {
44
+ expandedItems: ["x4"],
45
+ },
46
+ }));
47
+ expect(instanceBuilder).toBeCalled();
48
+ });
49
+ });
50
+
20
51
  describe("calls prev in correct order", () => {
21
52
  it("tree instance with overwrite marks, order 1", async () => {
22
53
  const testTree = await tree
@@ -137,6 +137,7 @@ export const createTree = <T>(
137
137
  // Not necessary, since I think the subupdate below keeps the state fresh anyways?
138
138
  // state = typeof updater === "function" ? updater(state) : updater;
139
139
  config.setState?.(state); // TODO this cant be right... This doesnt allow external state updates
140
+ // TODO this is never used, remove
140
141
  },
141
142
  applySubStateUpdate: <K extends keyof TreeState<any>>(
142
143
  {},
@@ -157,10 +158,20 @@ export const createTree = <T>(
157
158
  },
158
159
  getConfig: () => config,
159
160
  setConfig: (_, updater) => {
160
- config = typeof updater === "function" ? updater(config) : updater;
161
-
162
- if (config.state) {
163
- state = { ...state, ...config.state };
161
+ const newConfig =
162
+ typeof updater === "function" ? updater(config) : updater;
163
+ const hasChangedExpandedItems =
164
+ newConfig.state?.expandedItems &&
165
+ newConfig.state?.expandedItems !== state.expandedItems;
166
+ config = newConfig;
167
+
168
+ if (newConfig.state) {
169
+ state = { ...state, ...newConfig.state };
170
+ }
171
+ if (hasChangedExpandedItems) {
172
+ // if expanded items where changed from the outside
173
+ rebuildItemMeta();
174
+ config.setState?.(state);
164
175
  }
165
176
  },
166
177
  getItemInstance: ({}, itemId) => itemInstancesMap[itemId],
@@ -186,7 +197,6 @@ export const createTree = <T>(
186
197
  getHotkeyPresets: () => hotkeyPresets,
187
198
  },
188
199
  itemInstance: {
189
- // TODO just change to a getRef method that memoizes, maybe as part of getProps
190
200
  registerElement: ({ itemId, item }, element) => {
191
201
  if (itemElementsMap[itemId] === element) {
192
202
  return;