@headless-tree/core 0.0.10 → 0.0.12

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 (148) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/cjs/core/build-proxified-instance.d.ts +2 -0
  3. package/lib/cjs/core/build-proxified-instance.js +58 -0
  4. package/lib/cjs/core/build-static-instance.d.ts +2 -0
  5. package/lib/cjs/core/build-static-instance.js +26 -0
  6. package/lib/cjs/core/create-tree.js +62 -40
  7. package/lib/cjs/features/async-data-loader/feature.d.ts +1 -4
  8. package/lib/cjs/features/async-data-loader/feature.js +35 -23
  9. package/lib/cjs/features/async-data-loader/types.d.ts +4 -6
  10. package/lib/cjs/features/drag-and-drop/feature.d.ts +2 -3
  11. package/lib/cjs/features/drag-and-drop/feature.js +79 -44
  12. package/lib/cjs/features/drag-and-drop/types.d.ts +15 -6
  13. package/lib/cjs/features/drag-and-drop/utils.d.ts +2 -3
  14. package/lib/cjs/features/drag-and-drop/utils.js +140 -37
  15. package/lib/cjs/features/expand-all/feature.d.ts +1 -5
  16. package/lib/cjs/features/expand-all/feature.js +12 -6
  17. package/lib/cjs/features/hotkeys-core/feature.d.ts +1 -3
  18. package/lib/cjs/features/main/types.d.ts +8 -2
  19. package/lib/cjs/features/prop-memoization/feature.d.ts +2 -0
  20. package/lib/cjs/features/prop-memoization/feature.js +48 -0
  21. package/lib/cjs/features/prop-memoization/types.d.ts +10 -0
  22. package/lib/cjs/features/prop-memoization/types.js +2 -0
  23. package/lib/cjs/features/renaming/feature.d.ts +1 -4
  24. package/lib/cjs/features/renaming/feature.js +36 -22
  25. package/lib/cjs/features/renaming/types.d.ts +2 -2
  26. package/lib/cjs/features/search/feature.d.ts +1 -4
  27. package/lib/cjs/features/search/feature.js +38 -24
  28. package/lib/cjs/features/search/types.d.ts +0 -1
  29. package/lib/cjs/features/selection/feature.d.ts +1 -4
  30. package/lib/cjs/features/selection/feature.js +54 -35
  31. package/lib/cjs/features/selection/types.d.ts +1 -1
  32. package/lib/cjs/features/sync-data-loader/feature.d.ts +1 -3
  33. package/lib/cjs/features/sync-data-loader/feature.js +7 -2
  34. package/lib/cjs/features/tree/feature.d.ts +1 -5
  35. package/lib/cjs/features/tree/feature.js +97 -92
  36. package/lib/cjs/features/tree/types.d.ts +5 -8
  37. package/lib/cjs/index.d.ts +5 -1
  38. package/lib/cjs/index.js +4 -1
  39. package/lib/cjs/mddocs-entry.d.ts +10 -0
  40. package/lib/cjs/test-utils/test-tree-do.d.ts +23 -0
  41. package/lib/cjs/test-utils/test-tree-do.js +99 -0
  42. package/lib/cjs/test-utils/test-tree-expect.d.ts +15 -0
  43. package/lib/cjs/test-utils/test-tree-expect.js +62 -0
  44. package/lib/cjs/test-utils/test-tree.d.ts +47 -0
  45. package/lib/cjs/test-utils/test-tree.js +203 -0
  46. package/lib/cjs/types/core.d.ts +39 -24
  47. package/lib/cjs/utilities/errors.d.ts +1 -0
  48. package/lib/cjs/utilities/errors.js +5 -0
  49. package/lib/cjs/utilities/insert-items-at-target.js +10 -3
  50. package/lib/cjs/utilities/remove-items-from-parents.js +14 -8
  51. package/lib/cjs/utils.d.ts +3 -3
  52. package/lib/cjs/utils.js +6 -6
  53. package/lib/esm/core/build-proxified-instance.d.ts +2 -0
  54. package/lib/esm/core/build-proxified-instance.js +54 -0
  55. package/lib/esm/core/build-static-instance.d.ts +2 -0
  56. package/lib/esm/core/build-static-instance.js +22 -0
  57. package/lib/esm/core/create-tree.js +62 -40
  58. package/lib/esm/features/async-data-loader/feature.d.ts +1 -4
  59. package/lib/esm/features/async-data-loader/feature.js +35 -23
  60. package/lib/esm/features/async-data-loader/types.d.ts +4 -6
  61. package/lib/esm/features/drag-and-drop/feature.d.ts +2 -3
  62. package/lib/esm/features/drag-and-drop/feature.js +79 -44
  63. package/lib/esm/features/drag-and-drop/types.d.ts +15 -6
  64. package/lib/esm/features/drag-and-drop/utils.d.ts +2 -3
  65. package/lib/esm/features/drag-and-drop/utils.js +138 -34
  66. package/lib/esm/features/expand-all/feature.d.ts +1 -5
  67. package/lib/esm/features/expand-all/feature.js +12 -6
  68. package/lib/esm/features/hotkeys-core/feature.d.ts +1 -3
  69. package/lib/esm/features/main/types.d.ts +8 -2
  70. package/lib/esm/features/prop-memoization/feature.d.ts +2 -0
  71. package/lib/esm/features/prop-memoization/feature.js +45 -0
  72. package/lib/esm/features/prop-memoization/types.d.ts +10 -0
  73. package/lib/esm/features/prop-memoization/types.js +1 -0
  74. package/lib/esm/features/renaming/feature.d.ts +1 -4
  75. package/lib/esm/features/renaming/feature.js +36 -22
  76. package/lib/esm/features/renaming/types.d.ts +2 -2
  77. package/lib/esm/features/search/feature.d.ts +1 -4
  78. package/lib/esm/features/search/feature.js +38 -24
  79. package/lib/esm/features/search/types.d.ts +0 -1
  80. package/lib/esm/features/selection/feature.d.ts +1 -4
  81. package/lib/esm/features/selection/feature.js +54 -35
  82. package/lib/esm/features/selection/types.d.ts +1 -1
  83. package/lib/esm/features/sync-data-loader/feature.d.ts +1 -3
  84. package/lib/esm/features/sync-data-loader/feature.js +7 -2
  85. package/lib/esm/features/tree/feature.d.ts +1 -5
  86. package/lib/esm/features/tree/feature.js +98 -93
  87. package/lib/esm/features/tree/types.d.ts +5 -8
  88. package/lib/esm/index.d.ts +5 -1
  89. package/lib/esm/index.js +4 -1
  90. package/lib/esm/mddocs-entry.d.ts +10 -0
  91. package/lib/esm/test-utils/test-tree-do.d.ts +23 -0
  92. package/lib/esm/test-utils/test-tree-do.js +95 -0
  93. package/lib/esm/test-utils/test-tree-expect.d.ts +15 -0
  94. package/lib/esm/test-utils/test-tree-expect.js +58 -0
  95. package/lib/esm/test-utils/test-tree.d.ts +47 -0
  96. package/lib/esm/test-utils/test-tree.js +199 -0
  97. package/lib/esm/types/core.d.ts +39 -24
  98. package/lib/esm/utilities/errors.d.ts +1 -0
  99. package/lib/esm/utilities/errors.js +1 -0
  100. package/lib/esm/utilities/insert-items-at-target.js +10 -3
  101. package/lib/esm/utilities/remove-items-from-parents.js +14 -8
  102. package/lib/esm/utils.d.ts +3 -3
  103. package/lib/esm/utils.js +3 -3
  104. package/package.json +7 -3
  105. package/src/core/build-proxified-instance.ts +117 -0
  106. package/src/core/build-static-instance.ts +27 -0
  107. package/src/core/core.spec.ts +210 -0
  108. package/src/core/create-tree.ts +73 -78
  109. package/src/features/async-data-loader/async-data-loader.spec.ts +124 -0
  110. package/src/features/async-data-loader/feature.ts +34 -44
  111. package/src/features/async-data-loader/types.ts +4 -6
  112. package/src/features/drag-and-drop/drag-and-drop.spec.ts +717 -0
  113. package/src/features/drag-and-drop/feature.ts +88 -63
  114. package/src/features/drag-and-drop/types.ts +24 -10
  115. package/src/features/drag-and-drop/utils.ts +197 -56
  116. package/src/features/expand-all/expand-all.spec.ts +56 -0
  117. package/src/features/expand-all/feature.ts +9 -24
  118. package/src/features/hotkeys-core/feature.ts +5 -14
  119. package/src/features/main/types.ts +14 -1
  120. package/src/features/prop-memoization/feature.ts +51 -0
  121. package/src/features/prop-memoization/prop-memoization.spec.ts +68 -0
  122. package/src/features/prop-memoization/types.ts +11 -0
  123. package/src/features/renaming/feature.ts +37 -45
  124. package/src/features/renaming/renaming.spec.ts +127 -0
  125. package/src/features/renaming/types.ts +2 -2
  126. package/src/features/search/feature.ts +36 -46
  127. package/src/features/search/search.spec.ts +117 -0
  128. package/src/features/search/types.ts +0 -1
  129. package/src/features/selection/feature.ts +50 -53
  130. package/src/features/selection/selection.spec.ts +219 -0
  131. package/src/features/selection/types.ts +0 -2
  132. package/src/features/sync-data-loader/feature.ts +9 -18
  133. package/src/features/tree/feature.ts +101 -144
  134. package/src/features/tree/tree.spec.ts +475 -0
  135. package/src/features/tree/types.ts +5 -9
  136. package/src/index.ts +6 -1
  137. package/src/mddocs-entry.ts +13 -0
  138. package/src/test-utils/test-tree-do.ts +136 -0
  139. package/src/test-utils/test-tree-expect.ts +86 -0
  140. package/src/test-utils/test-tree.ts +227 -0
  141. package/src/types/core.ts +76 -108
  142. package/src/utilities/errors.ts +2 -0
  143. package/src/utilities/insert-items-at-target.ts +10 -3
  144. package/src/utilities/remove-items-from-parents.ts +15 -10
  145. package/src/utils.spec.ts +89 -0
  146. package/src/utils.ts +6 -6
  147. package/tsconfig.json +1 -0
  148. package/vitest.config.ts +6 -0
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { TestTree } from "../../test-utils/test-tree";
3
+ import { asyncDataLoaderFeature } from "./feature";
4
+ import { propMemoizationFeature } from "../prop-memoization/feature";
5
+
6
+ const tree = TestTree.default({}).withFeatures(
7
+ asyncDataLoaderFeature,
8
+ propMemoizationFeature,
9
+ );
10
+
11
+ describe("core-feature/selections", () => {
12
+ tree.resetBeforeEach();
13
+
14
+ describe("loading of items", () => {
15
+ it("has initial items", () => {
16
+ tree.expect.hasChildren("x", ["x1", "x2", "x3", "x4"]);
17
+ tree.expect.hasChildren("x1", ["x11", "x12", "x13", "x14"]);
18
+ tree.expect.hasChildren("x11", ["x111", "x112", "x113", "x114"]);
19
+ tree.expect.hasChildren("x12", []);
20
+ });
21
+
22
+ it.skip("has loading items after expanding", async () => {
23
+ tree.do.selectItem("x12");
24
+ await TestTree.resolveAsyncLoaders();
25
+ // tree.debug();
26
+ tree.expect.hasChildren("x12", [
27
+ "loading",
28
+ "loading",
29
+ "loading",
30
+ "loading",
31
+ ]);
32
+ });
33
+
34
+ it("has loaded items after expanding and loading", async () => {
35
+ tree.do.selectItem("x12");
36
+ await tree.resolveAsyncVisibleItems();
37
+ tree.expect.hasChildren("x12", ["x121", "x122", "x123", "x124"]);
38
+ tree.expect.hasChildren("x12", ["x121", "x122", "x123", "x124"]);
39
+ });
40
+ });
41
+
42
+ describe("calls handlers", () => {
43
+ it("updates setLoadingItems", async () => {
44
+ const setLoadingItems = tree.mockedHandler("setLoadingItems");
45
+ tree.do.selectItem("x12");
46
+ expect(setLoadingItems).toHaveBeenCalledWith(["x12"]);
47
+ await tree.resolveAsyncVisibleItems();
48
+ expect(setLoadingItems).toHaveBeenCalledWith([]);
49
+ });
50
+
51
+ it("calls onLoadedItem", async () => {
52
+ const onLoadedItem = tree.mockedHandler("onLoadedItem");
53
+ tree.do.selectItem("x12");
54
+ await tree.resolveAsyncVisibleItems();
55
+ expect(onLoadedItem).toHaveBeenCalledWith("x121", "x121");
56
+ });
57
+
58
+ it("calls onLoadedChildren", async () => {
59
+ const onLoadedChildren = tree.mockedHandler("onLoadedChildren");
60
+ tree.do.selectItem("x12");
61
+ await tree.resolveAsyncVisibleItems();
62
+ expect(onLoadedChildren).toHaveBeenCalledWith("x12", [
63
+ "x121",
64
+ "x122",
65
+ "x123",
66
+ "x124",
67
+ ]);
68
+ });
69
+ });
70
+
71
+ describe("data invalidation", () => {
72
+ const getItem = vi.fn(async (id) => id);
73
+ const getChildren = vi.fn(async (id) => [
74
+ `${id}1`,
75
+ `${id}2`,
76
+ `${id}3`,
77
+ `${id}4`,
78
+ ]);
79
+ const suiteTree = tree.with({ asyncDataLoader: { getItem, getChildren } });
80
+ suiteTree.resetBeforeEach();
81
+
82
+ it("invalidates item data on item instance", async () => {
83
+ getItem.mockClear();
84
+ await suiteTree.resolveAsyncVisibleItems();
85
+ getItem.mockResolvedValueOnce("new");
86
+ suiteTree.item("x1").invalidateItemData();
87
+ await suiteTree.resolveAsyncVisibleItems();
88
+ expect(getItem).toHaveBeenCalledWith("x1");
89
+ expect(suiteTree.item("x1").getItemData()).toBe("new");
90
+ });
91
+
92
+ it("invalidates children ids on item instance", async () => {
93
+ getChildren.mockClear();
94
+ await suiteTree.resolveAsyncVisibleItems();
95
+ getChildren.mockResolvedValueOnce(["new1", "new2"]);
96
+ suiteTree.item("x1").invalidateChildrenIds();
97
+ await suiteTree.resolveAsyncVisibleItems();
98
+ expect(getChildren).toHaveBeenCalledWith("x1");
99
+ suiteTree.expect.hasChildren("x1", ["new1", "new2"]);
100
+ });
101
+
102
+ it("doesnt call item data getter twice", async () => {
103
+ await suiteTree.resolveAsyncVisibleItems();
104
+ getItem.mockClear();
105
+ suiteTree.item("x1").invalidateItemData();
106
+ await suiteTree.resolveAsyncVisibleItems();
107
+ expect(suiteTree.item("x1").getItemData()).toBe("x1");
108
+ expect(suiteTree.item("x1").getItemData()).toBe("x1");
109
+ expect(getItem).toHaveBeenCalledTimes(1);
110
+ });
111
+
112
+ it("doesnt call children getter twice", async () => {
113
+ await suiteTree.resolveAsyncVisibleItems();
114
+ getChildren.mockClear();
115
+ suiteTree.item("x1").invalidateChildrenIds();
116
+ await suiteTree.resolveAsyncVisibleItems();
117
+ suiteTree.expect.hasChildren("x1", ["x11", "x12", "x13", "x14"]);
118
+ suiteTree.expect.hasChildren("x1", ["x11", "x12", "x13", "x14"]);
119
+ expect(getChildren).toHaveBeenCalledTimes(1);
120
+ });
121
+ });
122
+
123
+ describe.todo("getChildrenWithData");
124
+ });
@@ -1,14 +1,8 @@
1
1
  import { FeatureImplementation } from "../../types/core";
2
- import { AsyncDataLoaderFeatureDef, AsyncDataLoaderRef } from "./types";
3
- import { MainFeatureDef } from "../main/types";
2
+ import { AsyncDataLoaderRef } from "./types";
4
3
  import { makeStateUpdater } from "../../utils";
5
- import { TreeFeatureDef } from "../tree/types";
6
4
 
7
- export const asyncDataLoaderFeature: FeatureImplementation<
8
- any,
9
- AsyncDataLoaderFeatureDef<any>,
10
- MainFeatureDef | TreeFeatureDef<any> | AsyncDataLoaderFeatureDef<any>
11
- > = {
5
+ export const asyncDataLoaderFeature: FeatureImplementation = {
12
6
  key: "async-data-loader",
13
7
 
14
8
  getInitialState: (initialState) => ({
@@ -25,12 +19,10 @@ export const asyncDataLoaderFeature: FeatureImplementation<
25
19
  loadingItems: "setLoadingItems",
26
20
  },
27
21
 
28
- createTreeInstance: (prev, instance) => ({
29
- ...prev,
30
-
31
- retrieveItemData: (itemId) => {
32
- const config = instance.getConfig();
33
- const dataRef = instance.getDataRef<AsyncDataLoaderRef>();
22
+ treeInstance: {
23
+ retrieveItemData: ({ tree }, itemId) => {
24
+ const config = tree.getConfig();
25
+ const dataRef = tree.getDataRef<AsyncDataLoaderRef>();
34
26
  dataRef.current.itemData ??= {};
35
27
  dataRef.current.childrenIds ??= {};
36
28
 
@@ -38,15 +30,15 @@ export const asyncDataLoaderFeature: FeatureImplementation<
38
30
  return dataRef.current.itemData[itemId];
39
31
  }
40
32
 
41
- if (!instance.getState().loadingItems.includes(itemId)) {
42
- instance.applySubStateUpdate("loadingItems", (loadingItems) => [
33
+ if (!tree.getState().loadingItems.includes(itemId)) {
34
+ tree.applySubStateUpdate("loadingItems", (loadingItems) => [
43
35
  ...loadingItems,
44
36
  itemId,
45
37
  ]);
46
38
  config.asyncDataLoader?.getItem(itemId).then((item) => {
47
39
  dataRef.current.itemData[itemId] = item;
48
40
  config.onLoadedItem?.(itemId, item);
49
- instance.applySubStateUpdate("loadingItems", (loadingItems) =>
41
+ tree.applySubStateUpdate("loadingItems", (loadingItems) =>
50
42
  loadingItems.filter((id) => id !== itemId),
51
43
  );
52
44
  });
@@ -55,20 +47,20 @@ export const asyncDataLoaderFeature: FeatureImplementation<
55
47
  return config.createLoadingItemData?.() ?? null;
56
48
  },
57
49
 
58
- retrieveChildrenIds: (itemId) => {
59
- const config = instance.getConfig();
60
- const dataRef = instance.getDataRef<AsyncDataLoaderRef>();
50
+ retrieveChildrenIds: ({ tree }, itemId) => {
51
+ const config = tree.getConfig();
52
+ const dataRef = tree.getDataRef<AsyncDataLoaderRef>();
61
53
  dataRef.current.itemData ??= {};
62
54
  dataRef.current.childrenIds ??= {};
63
55
  if (dataRef.current.childrenIds[itemId]) {
64
56
  return dataRef.current.childrenIds[itemId];
65
57
  }
66
58
 
67
- if (instance.getState().loadingItems.includes(itemId)) {
59
+ if (tree.getState().loadingItems.includes(itemId)) {
68
60
  return [];
69
61
  }
70
62
 
71
- instance.applySubStateUpdate("loadingItems", (loadingItems) => [
63
+ tree.applySubStateUpdate("loadingItems", (loadingItems) => [
72
64
  ...loadingItems,
73
65
  itemId,
74
66
  ]);
@@ -82,45 +74,43 @@ export const asyncDataLoaderFeature: FeatureImplementation<
82
74
  const childrenIds = children.map(({ id }) => id);
83
75
  dataRef.current.childrenIds[itemId] = childrenIds;
84
76
  config.onLoadedChildren?.(itemId, childrenIds);
85
- instance.applySubStateUpdate("loadingItems", (loadingItems) =>
77
+ tree.applySubStateUpdate("loadingItems", (loadingItems) =>
86
78
  loadingItems.filter((id) => id !== itemId),
87
79
  );
88
- instance.rebuildTree();
80
+ tree.rebuildTree();
89
81
  });
90
82
  } else {
91
83
  config.asyncDataLoader?.getChildren(itemId).then((childrenIds) => {
92
84
  dataRef.current.childrenIds[itemId] = childrenIds;
93
85
  config.onLoadedChildren?.(itemId, childrenIds);
94
- instance.applySubStateUpdate("loadingItems", (loadingItems) =>
86
+ tree.applySubStateUpdate("loadingItems", (loadingItems) =>
95
87
  loadingItems.filter((id) => id !== itemId),
96
88
  );
97
- instance.rebuildTree();
89
+ tree.rebuildTree();
98
90
  });
99
91
  }
100
92
 
101
93
  return [];
102
94
  },
95
+ },
103
96
 
104
- invalidateItemData: (itemId) => {
105
- const dataRef = instance.getDataRef<AsyncDataLoaderRef>();
97
+ itemInstance: {
98
+ isLoading: ({ tree, item }) =>
99
+ tree.getState().loadingItems.includes(item.getItemMeta().itemId),
100
+ invalidateItemData: ({ tree, itemId }) => {
101
+ const dataRef = tree.getDataRef<AsyncDataLoaderRef>();
106
102
  delete dataRef.current.itemData?.[itemId];
107
- instance.retrieveItemData(itemId);
103
+ tree.retrieveItemData(itemId);
108
104
  },
109
-
110
- invalidateChildrenIds: (itemId) => {
111
- const dataRef = instance.getDataRef<AsyncDataLoaderRef>();
105
+ invalidateChildrenIds: ({ tree, itemId }) => {
106
+ const dataRef = tree.getDataRef<AsyncDataLoaderRef>();
112
107
  delete dataRef.current.childrenIds?.[itemId];
113
- instance.retrieveChildrenIds(itemId);
108
+ tree.retrieveChildrenIds(itemId);
114
109
  },
115
- }),
116
-
117
- createItemInstance: (prev, item, tree) => ({
118
- ...prev,
119
- isLoading: () =>
120
- tree.getState().loadingItems.includes(item.getItemMeta().itemId),
121
- invalidateItemData: () =>
122
- tree.invalidateItemData(item.getItemMeta().itemId),
123
- invalidateChildrenIds: () =>
124
- tree.invalidateChildrenIds(item.getItemMeta().itemId),
125
- }),
110
+ updateCachedChildrenIds: ({ tree, itemId }, childrenIds) => {
111
+ const dataRef = tree.getDataRef<AsyncDataLoaderRef>();
112
+ dataRef.current.childrenIds[itemId] = childrenIds;
113
+ tree.rebuildTree();
114
+ },
115
+ },
126
116
  };
@@ -27,15 +27,13 @@ export type AsyncDataLoaderFeatureDef<T> = {
27
27
  onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
28
28
  asyncDataLoader?: AsyncTreeDataLoader<T>;
29
29
  };
30
- treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
31
- /** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible */
32
- invalidateItemData: (itemId: string) => void;
33
- invalidateChildrenIds: (itemId: string) => void;
34
- };
30
+ treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"];
35
31
  itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
32
+ /** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible */
36
33
  invalidateItemData: () => void;
37
34
  invalidateChildrenIds: () => void;
38
- isLoading: () => void; // TODO! boolean?
35
+ updateCachedChildrenIds: (childrenIds: string[]) => void;
36
+ isLoading: () => boolean;
39
37
  };
40
38
  hotkeys: SyncDataLoaderFeatureDef<T>["hotkeys"];
41
39
  };