@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,117 @@
1
+ import { FeatureImplementation } from "../types/core";
2
+ import { InstanceBuilder, InstanceTypeMap } from "../features/main/types";
3
+ import { throwError } from "../utilities/errors";
4
+
5
+ const noop = () => {};
6
+
7
+ const findPrevInstanceMethod = (
8
+ features: FeatureImplementation[],
9
+ instanceType: keyof InstanceTypeMap,
10
+ methodKey: string,
11
+ featureSearchIndex: number,
12
+ ) => {
13
+ for (let i = featureSearchIndex; i >= 0; i--) {
14
+ const feature = features[i];
15
+ const itemInstanceMethod = (feature[instanceType] as any)?.[methodKey];
16
+ if (itemInstanceMethod) {
17
+ return i;
18
+ }
19
+ }
20
+ return null;
21
+ };
22
+
23
+ const invokeInstanceMethod = (
24
+ features: FeatureImplementation[],
25
+ instanceType: keyof InstanceTypeMap,
26
+ opts: any,
27
+ methodKey: string,
28
+ featureIndex: number,
29
+ args: any[],
30
+ ) => {
31
+ const prevIndex = findPrevInstanceMethod(
32
+ features,
33
+ instanceType,
34
+ methodKey,
35
+ featureIndex - 1,
36
+ );
37
+ const itemInstanceMethod = (features[featureIndex][instanceType] as any)?.[
38
+ methodKey
39
+ ]!;
40
+ return itemInstanceMethod(
41
+ {
42
+ ...opts,
43
+ prev:
44
+ prevIndex !== null
45
+ ? (...newArgs: any[]) =>
46
+ invokeInstanceMethod(
47
+ features,
48
+ instanceType,
49
+ opts,
50
+ methodKey,
51
+ prevIndex,
52
+ newArgs,
53
+ )
54
+ : null,
55
+ },
56
+ ...args,
57
+ );
58
+ };
59
+
60
+ export const buildProxiedInstance: InstanceBuilder = (
61
+ features,
62
+ instanceType,
63
+ buildOpts,
64
+ ) => {
65
+ // demo with prototypes: https://jsfiddle.net/bgenc58r/
66
+ const opts = {};
67
+ const item = new Proxy(
68
+ {},
69
+ {
70
+ has(target, key: string | symbol) {
71
+ if (typeof key === "symbol") {
72
+ return false;
73
+ }
74
+ if (key === "toJSON") {
75
+ return false;
76
+ }
77
+ const hasInstanceMethod = findPrevInstanceMethod(
78
+ features,
79
+ instanceType,
80
+ key,
81
+ features.length - 1,
82
+ );
83
+ return Boolean(hasInstanceMethod);
84
+ },
85
+ get(target, key: string | symbol) {
86
+ if (typeof key === "symbol") {
87
+ return undefined;
88
+ }
89
+ if (key === "toJSON") {
90
+ return {};
91
+ }
92
+ return (...args: any[]) => {
93
+ const featureIndex = findPrevInstanceMethod(
94
+ features,
95
+ instanceType,
96
+ key,
97
+ features.length - 1,
98
+ );
99
+
100
+ if (featureIndex === null) {
101
+ throw throwError(`feature missing for method ${key}`);
102
+ }
103
+ return invokeInstanceMethod(
104
+ features,
105
+ instanceType,
106
+ opts,
107
+ key,
108
+ featureIndex,
109
+ args,
110
+ );
111
+ };
112
+ },
113
+ },
114
+ );
115
+ Object.assign(opts, buildOpts(item));
116
+ return [item as any, noop];
117
+ };
@@ -0,0 +1,27 @@
1
+ /* eslint-disable no-continue,no-labels,no-extra-label */
2
+
3
+ import { InstanceBuilder } from "../features/main/types";
4
+
5
+ export const buildStaticInstance: InstanceBuilder = (
6
+ features,
7
+ instanceType,
8
+ buildOpts,
9
+ ) => {
10
+ const instance: any = {};
11
+ const finalize = () => {
12
+ const opts = buildOpts(instance);
13
+ featureLoop: for (let i = 0; i < features.length; i++) {
14
+ // Loop goes in forward order, each features overwrite previous ones and wraps those in a prev() fn
15
+ const definition = features[i][instanceType];
16
+ if (!definition) continue featureLoop;
17
+ methodLoop: for (const [key, method] of Object.entries(definition)) {
18
+ if (!method) continue methodLoop;
19
+ const prev = instance[key];
20
+ instance[key] = (...args: any[]) => {
21
+ return method({ ...opts, prev }, ...args);
22
+ };
23
+ }
24
+ }
25
+ };
26
+ return [instance as any, finalize];
27
+ };
@@ -0,0 +1,210 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { TestTree } from "../test-utils/test-tree";
3
+
4
+ declare module "../types/core" {
5
+ export interface TreeInstance<T> {
6
+ customHandler: (param1: number, param2: number) => void;
7
+ }
8
+ export interface ItemInstance<T> {
9
+ customHandler: (param1: number, param2: number) => void;
10
+ }
11
+ }
12
+
13
+ const handler1 = vi.fn(({ prev }, ...params) => prev?.(...params));
14
+ const handler2 = vi.fn(({ prev }, ...params) => prev?.(...params));
15
+
16
+ const factory = TestTree.default({});
17
+
18
+ describe("core-feature/prop-memoization", () => {
19
+ factory.forSuits((tree) => {
20
+ describe("calls prev in correct order", () => {
21
+ it("tree instance with overwrite marks, order 1", async () => {
22
+ const testTree = await tree
23
+ .withFeatures(
24
+ {
25
+ key: "feature2",
26
+ overwrites: ["feature1"],
27
+ treeInstance: {
28
+ customHandler: handler1,
29
+ },
30
+ },
31
+ {
32
+ key: "feature1",
33
+ treeInstance: {
34
+ customHandler: () => 123,
35
+ },
36
+ },
37
+ {
38
+ key: "feature3",
39
+ overwrites: ["feature2"],
40
+ treeInstance: {
41
+ customHandler: handler2,
42
+ },
43
+ },
44
+ )
45
+ .createTestCaseTree();
46
+
47
+ expect(testTree.instance.customHandler(1, 2)).toBe(123);
48
+ expect(handler2).toHaveBeenCalledBefore(handler1);
49
+ expect(handler1).toBeCalledWith(expect.anything(), 1, 2);
50
+ expect(handler2).toBeCalledWith(expect.anything(), 1, 2);
51
+ });
52
+
53
+ it("tree instance with overwrite marks, order 2", async () => {
54
+ const testTree = await tree
55
+ .withFeatures(
56
+ {
57
+ key: "feature3",
58
+ overwrites: ["feature2"],
59
+ treeInstance: {
60
+ customHandler: handler2,
61
+ },
62
+ },
63
+ {
64
+ key: "feature2",
65
+ overwrites: ["feature1"],
66
+ treeInstance: {
67
+ customHandler: handler1,
68
+ },
69
+ },
70
+ {
71
+ key: "feature1",
72
+ treeInstance: {
73
+ customHandler: () => 123,
74
+ },
75
+ },
76
+ )
77
+ .createTestCaseTree();
78
+
79
+ expect(testTree.instance.customHandler(1, 2)).toBe(123);
80
+ expect(handler2).toHaveBeenCalledBefore(handler1);
81
+ expect(handler1).toBeCalledWith(expect.anything(), 1, 2);
82
+ expect(handler2).toBeCalledWith(expect.anything(), 1, 2);
83
+ });
84
+
85
+ it("tree instance with implicit order", async () => {
86
+ const testTree = await tree
87
+ .withFeatures(
88
+ {
89
+ key: "feature1",
90
+ treeInstance: {
91
+ customHandler: () => 123,
92
+ },
93
+ },
94
+ {
95
+ key: "feature2",
96
+ treeInstance: {
97
+ customHandler: handler1,
98
+ },
99
+ },
100
+ {
101
+ key: "feature3",
102
+ treeInstance: {
103
+ customHandler: handler2,
104
+ },
105
+ },
106
+ )
107
+ .createTestCaseTree();
108
+
109
+ expect(testTree.instance.customHandler(1, 2)).toBe(123);
110
+ expect(handler2).toHaveBeenCalledBefore(handler1);
111
+ expect(handler1).toBeCalledWith(expect.anything(), 1, 2);
112
+ expect(handler2).toBeCalledWith(expect.anything(), 1, 2);
113
+ });
114
+
115
+ it("item instance with overwrite marks, order 1", async () => {
116
+ const testTree = await tree
117
+ .withFeatures(
118
+ {
119
+ key: "feature2",
120
+ overwrites: ["feature1"],
121
+ itemInstance: {
122
+ customHandler: handler1,
123
+ },
124
+ },
125
+ {
126
+ key: "feature1",
127
+ itemInstance: {
128
+ customHandler: () => 123,
129
+ },
130
+ },
131
+ {
132
+ key: "feature3",
133
+ overwrites: ["feature2"],
134
+ itemInstance: {
135
+ customHandler: handler2,
136
+ },
137
+ },
138
+ )
139
+ .createTestCaseTree();
140
+
141
+ expect(testTree.item("x111").customHandler(1, 2)).toBe(123);
142
+ expect(handler2).toHaveBeenCalledBefore(handler1);
143
+ expect(handler1).toBeCalledWith(expect.anything(), 1, 2);
144
+ expect(handler2).toBeCalledWith(expect.anything(), 1, 2);
145
+ });
146
+
147
+ it("item instance with overwrite marks, order 2", async () => {
148
+ const testTree = await tree
149
+ .withFeatures(
150
+ {
151
+ key: "feature3",
152
+ overwrites: ["feature2"],
153
+ itemInstance: {
154
+ customHandler: handler2,
155
+ },
156
+ },
157
+ {
158
+ key: "feature2",
159
+ overwrites: ["feature1"],
160
+ itemInstance: {
161
+ customHandler: handler1,
162
+ },
163
+ },
164
+ {
165
+ key: "feature1",
166
+ itemInstance: {
167
+ customHandler: () => 123,
168
+ },
169
+ },
170
+ )
171
+ .createTestCaseTree();
172
+
173
+ expect(testTree.item("x111").customHandler(1, 2)).toBe(123);
174
+ expect(handler2).toHaveBeenCalledBefore(handler1);
175
+ expect(handler1).toBeCalledWith(expect.anything(), 1, 2);
176
+ expect(handler2).toBeCalledWith(expect.anything(), 1, 2);
177
+ });
178
+
179
+ it("item instance with implicit order", async () => {
180
+ const testTree = await tree
181
+ .withFeatures(
182
+ {
183
+ key: "feature1",
184
+ itemInstance: {
185
+ customHandler: () => 123,
186
+ },
187
+ },
188
+ {
189
+ key: "feature2",
190
+ itemInstance: {
191
+ customHandler: handler1,
192
+ },
193
+ },
194
+ {
195
+ key: "feature3",
196
+ itemInstance: {
197
+ customHandler: handler2,
198
+ },
199
+ },
200
+ )
201
+ .createTestCaseTree();
202
+
203
+ expect(testTree.item("x111").customHandler(1, 2)).toBe(123);
204
+ expect(handler2).toHaveBeenCalledBefore(handler1);
205
+ expect(handler1).toBeCalledWith(expect.anything(), 1, 2);
206
+ expect(handler2).toBeCalledWith(expect.anything(), 1, 2);
207
+ });
208
+ });
209
+ });
210
+ });
@@ -5,32 +5,12 @@ import {
5
5
  TreeConfig,
6
6
  TreeInstance,
7
7
  TreeState,
8
+ Updater,
8
9
  } from "../types/core";
9
- import { MainFeatureDef } from "../features/main/types";
10
10
  import { treeFeature } from "../features/tree/feature";
11
11
  import { ItemMeta } from "../features/tree/types";
12
-
13
- const buildItemInstance = (
14
- features: FeatureImplementation[],
15
- tree: TreeInstance<any>,
16
- itemId: string,
17
- ) => {
18
- const itemInstance = {} as ItemInstance<any>;
19
- for (const feature of features) {
20
- Object.assign(
21
- // TODO dont run createItemInstance, but assign prototype objects instead?
22
- // https://jsfiddle.net/bgenc58r/
23
- itemInstance,
24
- feature.createItemInstance?.(
25
- { ...itemInstance },
26
- itemInstance,
27
- tree,
28
- itemId,
29
- ) ?? {},
30
- );
31
- }
32
- return itemInstance;
33
- };
12
+ import { buildStaticInstance } from "./build-static-instance";
13
+ import { throwError } from "../utilities/errors";
34
14
 
35
15
  const verifyFeatures = (features: FeatureImplementation[] | undefined) => {
36
16
  const loadedFeatures = features?.map((feature) => feature.key);
@@ -39,46 +19,55 @@ const verifyFeatures = (features: FeatureImplementation[] | undefined) => {
39
19
  (dep) => !loadedFeatures?.includes(dep),
40
20
  );
41
21
  if (missingDependency) {
42
- throw new Error(`${feature.key} needs ${missingDependency}`);
22
+ throw throwError(`${feature.key} needs ${missingDependency}`);
43
23
  }
44
24
  }
45
25
  };
46
26
 
47
- const compareFeatures = (
48
- feature1: FeatureImplementation,
49
- feature2: FeatureImplementation,
50
- ) => {
51
- if (feature2.key && feature1.overwrites?.includes(feature2.key)) {
52
- return 1;
53
- }
54
- return -1;
55
- };
27
+ const compareFeatures =
28
+ (originalOrder: FeatureImplementation[]) =>
29
+ (feature1: FeatureImplementation, feature2: FeatureImplementation) => {
30
+ if (feature2.key && feature1.overwrites?.includes(feature2.key)) {
31
+ return 1;
32
+ }
33
+ if (feature1.key && feature2.overwrites?.includes(feature1.key)) {
34
+ return -1;
35
+ }
36
+ return originalOrder.indexOf(feature1) - originalOrder.indexOf(feature2);
37
+ };
56
38
 
57
39
  const sortFeatures = (features: FeatureImplementation[] = []) =>
58
- features.sort(compareFeatures);
40
+ features.sort(compareFeatures(features));
59
41
 
60
42
  export const createTree = <T>(
61
43
  initialConfig: TreeConfig<T>,
62
44
  ): TreeInstance<T> => {
63
- const treeInstance: TreeInstance<T> = {} as any;
64
-
45
+ const buildInstance = initialConfig.instanceBuilder ?? buildStaticInstance;
65
46
  const additionalFeatures = [
66
47
  treeFeature,
67
48
  ...sortFeatures(initialConfig.features),
68
49
  ];
69
50
  verifyFeatures(additionalFeatures);
51
+ const features = [...additionalFeatures];
52
+
53
+ const [treeInstance, finalizeTree] = buildInstance(
54
+ features,
55
+ "treeInstance",
56
+ (tree) => ({ tree }),
57
+ );
70
58
 
71
59
  let state = additionalFeatures.reduce(
72
60
  (acc, feature) => feature.getInitialState?.(acc, treeInstance) ?? acc,
73
61
  initialConfig.initialState ?? initialConfig.state ?? {},
74
62
  ) as TreeState<T>;
75
63
  let config = additionalFeatures.reduce(
76
- (acc, feature) => feature.getDefaultConfig?.(acc, treeInstance) ?? acc,
64
+ (acc, feature) =>
65
+ (feature.getDefaultConfig?.(acc, treeInstance) as TreeConfig<T>) ?? acc,
77
66
  initialConfig,
78
67
  ) as TreeConfig<T>;
79
68
  const stateHandlerNames = additionalFeatures.reduce(
80
69
  (acc, feature) => ({ ...acc, ...feature.stateHandlerNames }),
81
- {} as Record<string, string>,
70
+ {} as Record<string, keyof TreeConfig<T>>,
82
71
  );
83
72
 
84
73
  let treeElement: HTMLElement | undefined | null;
@@ -92,16 +81,17 @@ export const createTree = <T>(
92
81
 
93
82
  const hotkeyPresets = {} as HotkeysConfig<T>;
94
83
 
95
- const rebuildItemMeta = (main: FeatureImplementation) => {
84
+ const rebuildItemMeta = () => {
96
85
  // TODO can we find a way to only run this for the changed substructure?
97
86
  itemInstances = [];
98
87
  itemMetaMap = {};
99
88
 
100
- const rootInstance = buildItemInstance(
101
- [main, ...additionalFeatures],
102
- treeInstance,
103
- config.rootItemId,
89
+ const [rootInstance, finalizeRootInstance] = buildInstance(
90
+ features,
91
+ "itemInstance",
92
+ (item) => ({ item, tree: treeInstance, itemId: config.rootItemId }),
104
93
  );
94
+ finalizeRootInstance();
105
95
  itemInstancesMap[config.rootItemId] = rootInstance;
106
96
  itemMetaMap[config.rootItemId] = {
107
97
  itemId: config.rootItemId,
@@ -115,11 +105,16 @@ export const createTree = <T>(
115
105
  for (const item of treeInstance.getItemsMeta()) {
116
106
  itemMetaMap[item.itemId] = item;
117
107
  if (!itemInstancesMap[item.itemId]) {
118
- const instance = buildItemInstance(
119
- [main, ...additionalFeatures],
120
- treeInstance,
121
- item.itemId,
108
+ const [instance, finalizeInstance] = buildInstance(
109
+ features,
110
+ "itemInstance",
111
+ (instance) => ({
112
+ item: instance,
113
+ tree: treeInstance,
114
+ itemId: item.itemId,
115
+ }),
122
116
  );
117
+ finalizeInstance();
123
118
  itemInstancesMap[item.itemId] = instance;
124
119
  itemInstances.push(instance);
125
120
  } else {
@@ -134,40 +129,43 @@ export const createTree = <T>(
134
129
  }
135
130
  };
136
131
 
137
- const mainFeature: FeatureImplementation<
138
- T,
139
- MainFeatureDef<T>,
140
- MainFeatureDef<T>
141
- > = {
132
+ const mainFeature: FeatureImplementation<T> = {
142
133
  key: "main",
143
- createTreeInstance: (prev) => ({
144
- ...prev,
134
+ treeInstance: {
145
135
  getState: () => state,
146
- setState: (updater) => {
136
+ setState: ({}, updater) => {
147
137
  // Not necessary, since I think the subupdate below keeps the state fresh anyways?
148
138
  // state = typeof updater === "function" ? updater(state) : updater;
149
- config.setState?.(state);
139
+ config.setState?.(state); // TODO this cant be right... This doesnt allow external state updates
150
140
  },
151
- applySubStateUpdate: (stateName, updater) => {
141
+ applySubStateUpdate: <K extends keyof TreeState<any>>(
142
+ {},
143
+ stateName: K,
144
+ updater: Updater<TreeState<T>[K]>,
145
+ ) => {
152
146
  state[stateName] =
153
147
  typeof updater === "function" ? updater(state[stateName]) : updater;
154
- config[stateHandlerNames[stateName]]!(state[stateName]);
148
+ const externalStateSetter = config[
149
+ stateHandlerNames[stateName]
150
+ ] as Function;
151
+ externalStateSetter?.(state[stateName]);
155
152
  },
153
+ // TODO rebuildSubTree: (itemId: string) => void;
156
154
  rebuildTree: () => {
157
- rebuildItemMeta(mainFeature);
155
+ rebuildItemMeta();
158
156
  config.setState?.(state);
159
157
  },
160
158
  getConfig: () => config,
161
- setConfig: (updater) => {
159
+ setConfig: (_, updater) => {
162
160
  config = typeof updater === "function" ? updater(config) : updater;
163
161
 
164
162
  if (config.state) {
165
163
  state = { ...state, ...config.state };
166
164
  }
167
165
  },
168
- getItemInstance: (itemId) => itemInstancesMap[itemId],
166
+ getItemInstance: ({}, itemId) => itemInstancesMap[itemId],
169
167
  getItems: () => itemInstances,
170
- registerElement: (element) => {
168
+ registerElement: ({}, element) => {
171
169
  if (treeElement === element) {
172
170
  return;
173
171
  }
@@ -186,10 +184,10 @@ export const createTree = <T>(
186
184
  getElement: () => treeElement,
187
185
  getDataRef: () => treeDataRef,
188
186
  getHotkeyPresets: () => hotkeyPresets,
189
- }),
190
- createItemInstance: (prev, instance, _, itemId) => ({
191
- ...prev,
192
- registerElement: (element) => {
187
+ },
188
+ itemInstance: {
189
+ // TODO just change to a getRef method that memoizes, maybe as part of getProps
190
+ registerElement: ({ itemId, item }, element) => {
193
191
  if (itemElementsMap[itemId] === element) {
194
192
  return;
195
193
  }
@@ -197,33 +195,30 @@ export const createTree = <T>(
197
195
  const oldElement = itemElementsMap[itemId];
198
196
  if (oldElement && !element) {
199
197
  eachFeature((feature) =>
200
- feature.onItemUnmount?.(instance, oldElement!, treeInstance),
198
+ feature.onItemUnmount?.(item, oldElement!, treeInstance),
201
199
  );
202
200
  } else if (!oldElement && element) {
203
201
  eachFeature((feature) =>
204
- feature.onItemMount?.(instance, element!, treeInstance),
202
+ feature.onItemMount?.(item, element!, treeInstance),
205
203
  );
206
204
  }
207
205
  itemElementsMap[itemId] = element;
208
206
  },
209
- getElement: () => itemElementsMap[itemId],
207
+ getElement: ({ itemId }) => itemElementsMap[itemId],
210
208
  // eslint-disable-next-line no-return-assign
211
- getDataRef: () => (itemDataRefs[itemId] ??= { current: {} }),
212
- getItemMeta: () => itemMetaMap[itemId],
213
- }),
209
+ getDataRef: ({ itemId }) => (itemDataRefs[itemId] ??= { current: {} }),
210
+ getItemMeta: ({ itemId }) => itemMetaMap[itemId],
211
+ },
214
212
  };
215
213
 
216
- const features = [mainFeature, ...additionalFeatures];
214
+ features.unshift(mainFeature);
217
215
 
218
216
  for (const feature of features) {
219
- Object.assign(
220
- treeInstance,
221
- feature.createTreeInstance?.({ ...treeInstance }, treeInstance) ?? {},
222
- );
223
217
  Object.assign(hotkeyPresets, feature.hotkeys ?? {});
224
218
  }
225
219
 
226
- rebuildItemMeta(mainFeature);
220
+ finalizeTree();
221
+ rebuildItemMeta();
227
222
 
228
223
  return treeInstance;
229
224
  };