@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.
- package/CHANGELOG.md +12 -0
- package/lib/cjs/core/build-proxified-instance.d.ts +2 -0
- package/lib/cjs/core/build-proxified-instance.js +58 -0
- package/lib/cjs/core/build-static-instance.d.ts +2 -0
- package/lib/cjs/core/build-static-instance.js +26 -0
- package/lib/cjs/core/create-tree.js +62 -40
- package/lib/cjs/features/async-data-loader/feature.d.ts +1 -4
- package/lib/cjs/features/async-data-loader/feature.js +35 -23
- package/lib/cjs/features/async-data-loader/types.d.ts +4 -6
- package/lib/cjs/features/drag-and-drop/feature.d.ts +2 -3
- package/lib/cjs/features/drag-and-drop/feature.js +79 -44
- package/lib/cjs/features/drag-and-drop/types.d.ts +15 -6
- package/lib/cjs/features/drag-and-drop/utils.d.ts +2 -3
- package/lib/cjs/features/drag-and-drop/utils.js +140 -37
- package/lib/cjs/features/expand-all/feature.d.ts +1 -5
- package/lib/cjs/features/expand-all/feature.js +12 -6
- package/lib/cjs/features/hotkeys-core/feature.d.ts +1 -3
- package/lib/cjs/features/main/types.d.ts +8 -2
- package/lib/cjs/features/prop-memoization/feature.d.ts +2 -0
- package/lib/cjs/features/prop-memoization/feature.js +48 -0
- package/lib/cjs/features/prop-memoization/types.d.ts +10 -0
- package/lib/cjs/features/prop-memoization/types.js +2 -0
- package/lib/cjs/features/renaming/feature.d.ts +1 -4
- package/lib/cjs/features/renaming/feature.js +36 -22
- package/lib/cjs/features/renaming/types.d.ts +2 -2
- package/lib/cjs/features/search/feature.d.ts +1 -4
- package/lib/cjs/features/search/feature.js +38 -24
- package/lib/cjs/features/search/types.d.ts +0 -1
- package/lib/cjs/features/selection/feature.d.ts +1 -4
- package/lib/cjs/features/selection/feature.js +54 -35
- package/lib/cjs/features/selection/types.d.ts +1 -1
- package/lib/cjs/features/sync-data-loader/feature.d.ts +1 -3
- package/lib/cjs/features/sync-data-loader/feature.js +7 -2
- package/lib/cjs/features/tree/feature.d.ts +1 -5
- package/lib/cjs/features/tree/feature.js +97 -92
- package/lib/cjs/features/tree/types.d.ts +5 -8
- package/lib/cjs/index.d.ts +5 -1
- package/lib/cjs/index.js +4 -1
- package/lib/cjs/mddocs-entry.d.ts +10 -0
- package/lib/cjs/test-utils/test-tree-do.d.ts +23 -0
- package/lib/cjs/test-utils/test-tree-do.js +99 -0
- package/lib/cjs/test-utils/test-tree-expect.d.ts +15 -0
- package/lib/cjs/test-utils/test-tree-expect.js +62 -0
- package/lib/cjs/test-utils/test-tree.d.ts +47 -0
- package/lib/cjs/test-utils/test-tree.js +203 -0
- package/lib/cjs/types/core.d.ts +39 -24
- package/lib/cjs/utilities/errors.d.ts +1 -0
- package/lib/cjs/utilities/errors.js +5 -0
- package/lib/cjs/utilities/insert-items-at-target.js +10 -3
- package/lib/cjs/utilities/remove-items-from-parents.js +14 -8
- package/lib/cjs/utils.d.ts +3 -3
- package/lib/cjs/utils.js +6 -6
- package/lib/esm/core/build-proxified-instance.d.ts +2 -0
- package/lib/esm/core/build-proxified-instance.js +54 -0
- package/lib/esm/core/build-static-instance.d.ts +2 -0
- package/lib/esm/core/build-static-instance.js +22 -0
- package/lib/esm/core/create-tree.js +62 -40
- package/lib/esm/features/async-data-loader/feature.d.ts +1 -4
- package/lib/esm/features/async-data-loader/feature.js +35 -23
- package/lib/esm/features/async-data-loader/types.d.ts +4 -6
- package/lib/esm/features/drag-and-drop/feature.d.ts +2 -3
- package/lib/esm/features/drag-and-drop/feature.js +79 -44
- package/lib/esm/features/drag-and-drop/types.d.ts +15 -6
- package/lib/esm/features/drag-and-drop/utils.d.ts +2 -3
- package/lib/esm/features/drag-and-drop/utils.js +138 -34
- package/lib/esm/features/expand-all/feature.d.ts +1 -5
- package/lib/esm/features/expand-all/feature.js +12 -6
- package/lib/esm/features/hotkeys-core/feature.d.ts +1 -3
- package/lib/esm/features/main/types.d.ts +8 -2
- package/lib/esm/features/prop-memoization/feature.d.ts +2 -0
- package/lib/esm/features/prop-memoization/feature.js +45 -0
- package/lib/esm/features/prop-memoization/types.d.ts +10 -0
- package/lib/esm/features/prop-memoization/types.js +1 -0
- package/lib/esm/features/renaming/feature.d.ts +1 -4
- package/lib/esm/features/renaming/feature.js +36 -22
- package/lib/esm/features/renaming/types.d.ts +2 -2
- package/lib/esm/features/search/feature.d.ts +1 -4
- package/lib/esm/features/search/feature.js +38 -24
- package/lib/esm/features/search/types.d.ts +0 -1
- package/lib/esm/features/selection/feature.d.ts +1 -4
- package/lib/esm/features/selection/feature.js +54 -35
- package/lib/esm/features/selection/types.d.ts +1 -1
- package/lib/esm/features/sync-data-loader/feature.d.ts +1 -3
- package/lib/esm/features/sync-data-loader/feature.js +7 -2
- package/lib/esm/features/tree/feature.d.ts +1 -5
- package/lib/esm/features/tree/feature.js +98 -93
- package/lib/esm/features/tree/types.d.ts +5 -8
- package/lib/esm/index.d.ts +5 -1
- package/lib/esm/index.js +4 -1
- package/lib/esm/mddocs-entry.d.ts +10 -0
- package/lib/esm/test-utils/test-tree-do.d.ts +23 -0
- package/lib/esm/test-utils/test-tree-do.js +95 -0
- package/lib/esm/test-utils/test-tree-expect.d.ts +15 -0
- package/lib/esm/test-utils/test-tree-expect.js +58 -0
- package/lib/esm/test-utils/test-tree.d.ts +47 -0
- package/lib/esm/test-utils/test-tree.js +199 -0
- package/lib/esm/types/core.d.ts +39 -24
- package/lib/esm/utilities/errors.d.ts +1 -0
- package/lib/esm/utilities/errors.js +1 -0
- package/lib/esm/utilities/insert-items-at-target.js +10 -3
- package/lib/esm/utilities/remove-items-from-parents.js +14 -8
- package/lib/esm/utils.d.ts +3 -3
- package/lib/esm/utils.js +3 -3
- package/package.json +7 -3
- package/src/core/build-proxified-instance.ts +117 -0
- package/src/core/build-static-instance.ts +27 -0
- package/src/core/core.spec.ts +210 -0
- package/src/core/create-tree.ts +73 -78
- package/src/features/async-data-loader/async-data-loader.spec.ts +124 -0
- package/src/features/async-data-loader/feature.ts +34 -44
- package/src/features/async-data-loader/types.ts +4 -6
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +717 -0
- package/src/features/drag-and-drop/feature.ts +88 -63
- package/src/features/drag-and-drop/types.ts +24 -10
- package/src/features/drag-and-drop/utils.ts +197 -56
- package/src/features/expand-all/expand-all.spec.ts +56 -0
- package/src/features/expand-all/feature.ts +9 -24
- package/src/features/hotkeys-core/feature.ts +5 -14
- package/src/features/main/types.ts +14 -1
- package/src/features/prop-memoization/feature.ts +51 -0
- package/src/features/prop-memoization/prop-memoization.spec.ts +68 -0
- package/src/features/prop-memoization/types.ts +11 -0
- package/src/features/renaming/feature.ts +37 -45
- package/src/features/renaming/renaming.spec.ts +127 -0
- package/src/features/renaming/types.ts +2 -2
- package/src/features/search/feature.ts +36 -46
- package/src/features/search/search.spec.ts +117 -0
- package/src/features/search/types.ts +0 -1
- package/src/features/selection/feature.ts +50 -53
- package/src/features/selection/selection.spec.ts +219 -0
- package/src/features/selection/types.ts +0 -2
- package/src/features/sync-data-loader/feature.ts +9 -18
- package/src/features/tree/feature.ts +101 -144
- package/src/features/tree/tree.spec.ts +475 -0
- package/src/features/tree/types.ts +5 -9
- package/src/index.ts +6 -1
- package/src/mddocs-entry.ts +13 -0
- package/src/test-utils/test-tree-do.ts +136 -0
- package/src/test-utils/test-tree-expect.ts +86 -0
- package/src/test-utils/test-tree.ts +227 -0
- package/src/types/core.ts +76 -108
- package/src/utilities/errors.ts +2 -0
- package/src/utilities/insert-items-at-target.ts +10 -3
- package/src/utilities/remove-items-from-parents.ts +15 -10
- package/src/utils.spec.ts +89 -0
- package/src/utils.ts +6 -6
- package/tsconfig.json +1 -0
- 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
|
+
});
|
package/src/core/create-tree.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
22
|
+
throw throwError(`${feature.key} needs ${missingDependency}`);
|
|
43
23
|
}
|
|
44
24
|
}
|
|
45
25
|
};
|
|
46
26
|
|
|
47
|
-
const compareFeatures =
|
|
48
|
-
|
|
49
|
-
feature2: FeatureImplementation
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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) =>
|
|
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,
|
|
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 = (
|
|
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 =
|
|
101
|
-
|
|
102
|
-
|
|
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 =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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:
|
|
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[
|
|
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(
|
|
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
|
-
|
|
191
|
-
|
|
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?.(
|
|
198
|
+
feature.onItemUnmount?.(item, oldElement!, treeInstance),
|
|
201
199
|
);
|
|
202
200
|
} else if (!oldElement && element) {
|
|
203
201
|
eachFeature((feature) =>
|
|
204
|
-
feature.onItemMount?.(
|
|
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
|
-
|
|
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
|
-
|
|
220
|
+
finalizeTree();
|
|
221
|
+
rebuildItemMeta();
|
|
227
222
|
|
|
228
223
|
return treeInstance;
|
|
229
224
|
};
|