@headless-tree/core 0.0.11 → 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 +6 -0
- package/lib/cjs/core/build-static-instance.js +1 -2
- package/lib/cjs/core/create-tree.js +7 -4
- package/lib/cjs/features/async-data-loader/feature.d.ts +1 -4
- package/lib/cjs/features/async-data-loader/feature.js +5 -7
- package/lib/cjs/features/async-data-loader/types.d.ts +2 -5
- package/lib/cjs/features/drag-and-drop/feature.d.ts +2 -3
- package/lib/cjs/features/drag-and-drop/feature.js +27 -24
- package/lib/cjs/features/drag-and-drop/types.d.ts +3 -3
- package/lib/cjs/features/drag-and-drop/utils.d.ts +1 -1
- package/lib/cjs/features/drag-and-drop/utils.js +4 -4
- package/lib/cjs/features/expand-all/feature.d.ts +1 -5
- package/lib/cjs/features/hotkeys-core/feature.d.ts +1 -3
- 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 +8 -9
- package/lib/cjs/features/renaming/types.d.ts +1 -1
- package/lib/cjs/features/search/feature.d.ts +1 -4
- package/lib/cjs/features/selection/feature.d.ts +1 -4
- package/lib/cjs/features/selection/feature.js +35 -25
- 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/tree/feature.d.ts +1 -6
- package/lib/cjs/features/tree/feature.js +40 -57
- package/lib/cjs/features/tree/types.d.ts +0 -5
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/mddocs-entry.d.ts +10 -0
- package/lib/cjs/test-utils/test-tree-do.d.ts +1 -1
- package/lib/cjs/test-utils/test-tree-expect.d.ts +1 -1
- package/lib/cjs/test-utils/test-tree-expect.js +1 -1
- package/lib/cjs/test-utils/test-tree.d.ts +1 -1
- package/lib/cjs/test-utils/test-tree.js +9 -1
- package/lib/cjs/types/core.d.ts +29 -30
- package/lib/esm/core/build-static-instance.js +1 -2
- package/lib/esm/core/create-tree.js +7 -4
- package/lib/esm/features/async-data-loader/feature.d.ts +1 -4
- package/lib/esm/features/async-data-loader/feature.js +5 -7
- package/lib/esm/features/async-data-loader/types.d.ts +2 -5
- package/lib/esm/features/drag-and-drop/feature.d.ts +2 -3
- package/lib/esm/features/drag-and-drop/feature.js +27 -24
- package/lib/esm/features/drag-and-drop/types.d.ts +3 -3
- package/lib/esm/features/drag-and-drop/utils.d.ts +1 -1
- package/lib/esm/features/drag-and-drop/utils.js +4 -4
- package/lib/esm/features/expand-all/feature.d.ts +1 -5
- package/lib/esm/features/hotkeys-core/feature.d.ts +1 -3
- 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 +8 -9
- package/lib/esm/features/renaming/types.d.ts +1 -1
- package/lib/esm/features/search/feature.d.ts +1 -4
- package/lib/esm/features/selection/feature.d.ts +1 -4
- package/lib/esm/features/selection/feature.js +35 -25
- 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/tree/feature.d.ts +1 -6
- package/lib/esm/features/tree/feature.js +40 -57
- package/lib/esm/features/tree/types.d.ts +0 -5
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/mddocs-entry.d.ts +10 -0
- package/lib/esm/test-utils/test-tree-do.d.ts +1 -1
- package/lib/esm/test-utils/test-tree-expect.d.ts +1 -1
- package/lib/esm/test-utils/test-tree-expect.js +1 -1
- package/lib/esm/test-utils/test-tree.d.ts +1 -1
- package/lib/esm/test-utils/test-tree.js +9 -1
- package/lib/esm/types/core.d.ts +29 -30
- package/package.json +1 -1
- package/src/core/build-proxified-instance.ts +5 -3
- package/src/core/build-static-instance.ts +1 -2
- package/src/core/core.spec.ts +210 -0
- package/src/core/create-tree.ts +13 -16
- package/src/features/async-data-loader/async-data-loader.spec.ts +12 -31
- package/src/features/async-data-loader/feature.ts +8 -20
- package/src/features/async-data-loader/types.ts +2 -6
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +4 -3
- package/src/features/drag-and-drop/feature.ts +87 -86
- package/src/features/drag-and-drop/types.ts +4 -4
- package/src/features/drag-and-drop/utils.ts +4 -4
- package/src/features/expand-all/expand-all.spec.ts +5 -1
- package/src/features/expand-all/feature.ts +1 -12
- package/src/features/hotkeys-core/feature.ts +4 -13
- 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 +11 -20
- package/src/features/renaming/renaming.spec.ts +11 -9
- package/src/features/renaming/types.ts +1 -1
- package/src/features/search/feature.ts +2 -8
- package/src/features/search/search.spec.ts +3 -1
- package/src/features/selection/feature.ts +45 -47
- package/src/features/selection/selection.spec.ts +13 -14
- package/src/features/selection/types.ts +0 -2
- package/src/features/sync-data-loader/feature.ts +1 -7
- package/src/features/tree/feature.ts +47 -85
- package/src/features/tree/tree.spec.ts +24 -64
- package/src/features/tree/types.ts +0 -6
- package/src/index.ts +2 -0
- package/src/mddocs-entry.ts +13 -0
- package/src/test-utils/test-tree-expect.ts +1 -1
- package/src/test-utils/test-tree.ts +11 -1
- package/src/types/core.ts +56 -147
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DndDataRef,
|
|
1
|
+
import { FeatureImplementation } from "../../types/core";
|
|
2
|
+
import { DndDataRef, DragLineData } from "./types";
|
|
3
3
|
import { canDrop, getDragCode, getDropTarget } from "./utils";
|
|
4
4
|
import { makeStateUpdater } from "../../utils";
|
|
5
5
|
|
|
6
|
-
export const dragAndDropFeature: FeatureImplementation
|
|
7
|
-
|
|
8
|
-
DragAndDropFeatureDef<any>,
|
|
9
|
-
FeatureDefs<any>
|
|
10
|
-
> = {
|
|
11
|
-
key: "dragAndDrop",
|
|
6
|
+
export const dragAndDropFeature: FeatureImplementation = {
|
|
7
|
+
key: "drag-and-drop",
|
|
12
8
|
deps: ["selection"],
|
|
13
9
|
|
|
14
10
|
getDefaultConfig: (defaultConfig, tree) => ({
|
|
15
11
|
canDrop: (_, target) => target.item.isFolder(),
|
|
16
12
|
canDropForeignDragObject: () => false,
|
|
17
13
|
setDndState: makeStateUpdater("dnd", tree),
|
|
18
|
-
|
|
14
|
+
canReorder: true,
|
|
19
15
|
...defaultConfig,
|
|
20
16
|
}),
|
|
21
17
|
|
|
@@ -29,11 +25,12 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
29
25
|
},
|
|
30
26
|
|
|
31
27
|
getDragLineData: ({ tree }): DragLineData | null => {
|
|
32
|
-
// TODO doesnt work if scrolled down!
|
|
33
28
|
const target = tree.getDropTarget();
|
|
34
|
-
const indent = (target?.item.getItemMeta().level ?? 0) + 1;
|
|
29
|
+
const indent = (target?.item.getItemMeta().level ?? 0) + 1;
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
const treeBb = tree.getElement()?.getBoundingClientRect();
|
|
32
|
+
|
|
33
|
+
if (!target || !treeBb || target.childIndex === null) return null;
|
|
37
34
|
|
|
38
35
|
const leftOffset = target.dragLineLevel * (tree.getConfig().indent ?? 1);
|
|
39
36
|
const targetItem = tree.getItems()[target.dragLineIndex];
|
|
@@ -47,9 +44,9 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
47
44
|
if (bb) {
|
|
48
45
|
return {
|
|
49
46
|
indent,
|
|
50
|
-
top: bb.bottom,
|
|
51
|
-
left: bb.left + leftOffset,
|
|
52
|
-
|
|
47
|
+
top: bb.bottom - treeBb.bottom,
|
|
48
|
+
left: bb.left + leftOffset - treeBb.left,
|
|
49
|
+
width: bb.width - leftOffset,
|
|
53
50
|
};
|
|
54
51
|
}
|
|
55
52
|
}
|
|
@@ -59,9 +56,9 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
59
56
|
if (bb) {
|
|
60
57
|
return {
|
|
61
58
|
indent,
|
|
62
|
-
top: bb.top,
|
|
63
|
-
left: bb.left + leftOffset,
|
|
64
|
-
|
|
59
|
+
top: bb.top - treeBb.top,
|
|
60
|
+
left: bb.left + leftOffset - treeBb.left,
|
|
61
|
+
width: bb.width - leftOffset,
|
|
65
62
|
};
|
|
66
63
|
}
|
|
67
64
|
|
|
@@ -74,90 +71,94 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
74
71
|
? {
|
|
75
72
|
top: `${dragLine.top + topOffset}px`,
|
|
76
73
|
left: `${dragLine.left + leftOffset}px`,
|
|
77
|
-
width: `${dragLine.
|
|
74
|
+
width: `${dragLine.width - leftOffset}px`,
|
|
78
75
|
pointerEvents: "none", // important to prevent capturing drag events
|
|
79
76
|
}
|
|
80
77
|
: { display: "none" };
|
|
81
78
|
},
|
|
79
|
+
|
|
80
|
+
getContainerProps: ({ prev }) => {
|
|
81
|
+
const prevProps = prev?.();
|
|
82
|
+
return {
|
|
83
|
+
...prevProps,
|
|
84
|
+
style: {
|
|
85
|
+
...prevProps?.style,
|
|
86
|
+
position: "relative",
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
},
|
|
82
90
|
},
|
|
83
91
|
|
|
84
92
|
itemInstance: {
|
|
85
|
-
// TODO instead of individual getMemoizedProp calls, use a wrapped getMemoizedProps or something (getProps: () => getMemoized({...})
|
|
86
93
|
getProps: ({ tree, item, prev }) => ({
|
|
87
94
|
...prev?.(),
|
|
88
95
|
|
|
89
96
|
draggable: tree.getConfig().isItemDraggable?.(item) ?? true,
|
|
90
97
|
|
|
91
|
-
onDragStart:
|
|
92
|
-
|
|
93
|
-
()
|
|
94
|
-
|
|
95
|
-
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
96
|
-
const config = tree.getConfig();
|
|
98
|
+
onDragStart: (e: DragEvent) => {
|
|
99
|
+
const selectedItems = tree.getSelectedItems();
|
|
100
|
+
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
101
|
+
const config = tree.getConfig();
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
if (!selectedItems.includes(item)) {
|
|
104
|
+
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
105
|
+
}
|
|
101
106
|
|
|
102
|
-
|
|
107
|
+
if (!(config.canDrag?.(items) ?? true)) {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (config.createForeignDragObject) {
|
|
113
|
+
const { format, data } = config.createForeignDragObject(items);
|
|
114
|
+
e.dataTransfer?.setData(format, data);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
tree.applySubStateUpdate("dnd", {
|
|
118
|
+
draggedItems: items,
|
|
119
|
+
draggingOverItem: tree.getFocusedItem(),
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
onDragOver: (e: DragEvent) => {
|
|
124
|
+
const dataRef = tree.getDataRef<DndDataRef>();
|
|
125
|
+
const nextDragCode = getDragCode(e, item, tree);
|
|
126
|
+
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
127
|
+
if (dataRef.current.lastAllowDrop) {
|
|
103
128
|
e.preventDefault();
|
|
104
|
-
return;
|
|
105
129
|
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
dataRef.current.lastDragCode = nextDragCode;
|
|
106
133
|
|
|
107
|
-
|
|
108
|
-
const { format, data } = config.createForeignDragObject(items);
|
|
109
|
-
e.dataTransfer?.setData(format, data);
|
|
110
|
-
}
|
|
134
|
+
const target = getDropTarget(e, item, tree);
|
|
111
135
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const dataRef = tree.getDataRef<DndDataRef>();
|
|
123
|
-
const nextDragCode = getDragCode(e, item, tree);
|
|
124
|
-
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
125
|
-
if (dataRef.current.lastAllowDrop) {
|
|
126
|
-
e.preventDefault();
|
|
127
|
-
}
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
dataRef.current.lastDragCode = nextDragCode;
|
|
131
|
-
|
|
132
|
-
const target = getDropTarget(e, item, tree);
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
!tree.getState().dnd?.draggedItems &&
|
|
136
|
-
(!e.dataTransfer ||
|
|
137
|
-
!tree
|
|
138
|
-
.getConfig()
|
|
139
|
-
.canDropForeignDragObject?.(e.dataTransfer, target))
|
|
140
|
-
) {
|
|
141
|
-
dataRef.current.lastAllowDrop = false;
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
136
|
+
if (
|
|
137
|
+
!tree.getState().dnd?.draggedItems &&
|
|
138
|
+
(!e.dataTransfer ||
|
|
139
|
+
!tree
|
|
140
|
+
.getConfig()
|
|
141
|
+
.canDropForeignDragObject?.(e.dataTransfer, target))
|
|
142
|
+
) {
|
|
143
|
+
dataRef.current.lastAllowDrop = false;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
144
146
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
if (!canDrop(e.dataTransfer, target, tree)) {
|
|
148
|
+
dataRef.current.lastAllowDrop = false;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
),
|
|
152
|
+
tree.applySubStateUpdate("dnd", (state) => ({
|
|
153
|
+
...state,
|
|
154
|
+
dragTarget: target,
|
|
155
|
+
draggingOverItem: item,
|
|
156
|
+
}));
|
|
157
|
+
dataRef.current.lastAllowDrop = true;
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
},
|
|
159
160
|
|
|
160
|
-
onDragLeave:
|
|
161
|
+
onDragLeave: () => {
|
|
161
162
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
162
163
|
dataRef.current.lastDragCode = "no-drag";
|
|
163
164
|
tree.applySubStateUpdate("dnd", (state) => ({
|
|
@@ -165,9 +166,9 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
165
166
|
draggingOverItem: undefined,
|
|
166
167
|
dragTarget: undefined,
|
|
167
168
|
}));
|
|
168
|
-
}
|
|
169
|
+
},
|
|
169
170
|
|
|
170
|
-
onDragEnd:
|
|
171
|
+
onDragEnd: (e: DragEvent) => {
|
|
171
172
|
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
172
173
|
tree.applySubStateUpdate("dnd", null);
|
|
173
174
|
|
|
@@ -176,9 +177,9 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
tree.getConfig().onCompleteForeignDrop?.(draggedItems);
|
|
179
|
-
}
|
|
180
|
+
},
|
|
180
181
|
|
|
181
|
-
onDrop:
|
|
182
|
+
onDrop: (e: DragEvent) => {
|
|
182
183
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
183
184
|
const target = getDropTarget(e, item, tree);
|
|
184
185
|
|
|
@@ -199,7 +200,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
199
200
|
config.onDropForeignDragObject?.(e.dataTransfer, target);
|
|
200
201
|
}
|
|
201
202
|
// TODO rebuild tree?
|
|
202
|
-
}
|
|
203
|
+
},
|
|
203
204
|
}),
|
|
204
205
|
|
|
205
206
|
isDropTarget: ({ tree, item }) => {
|
|
@@ -6,7 +6,7 @@ export type DndDataRef = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export type DndState<T> = {
|
|
9
|
-
draggedItems?: ItemInstance<T>[];
|
|
9
|
+
draggedItems?: ItemInstance<T>[];
|
|
10
10
|
draggingOverItem?: ItemInstance<T>;
|
|
11
11
|
dragTarget?: DropTarget<T>;
|
|
12
12
|
};
|
|
@@ -15,7 +15,7 @@ export type DragLineData = {
|
|
|
15
15
|
indent: number;
|
|
16
16
|
top: number;
|
|
17
17
|
left: number;
|
|
18
|
-
|
|
18
|
+
width: number;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export type DropTarget<T> =
|
|
@@ -49,9 +49,9 @@ export type DragAndDropFeatureDef<T> = {
|
|
|
49
49
|
|
|
50
50
|
/** Defines the size of the area at the top and bottom of an item where, when an item is dropped, the item willö
|
|
51
51
|
* be placed above or below the item within the same parent, as opposed to being placed inside the item.
|
|
52
|
-
* If `
|
|
52
|
+
* If `canReorder` is `false`, this is ignored. */
|
|
53
53
|
reorderAreaPercentage?: number;
|
|
54
|
-
|
|
54
|
+
canReorder?: boolean;
|
|
55
55
|
|
|
56
56
|
// TODO better document difference to canDrag(), or unify both
|
|
57
57
|
isItemDraggable?: (item: ItemInstance<T>) => boolean;
|
|
@@ -81,7 +81,7 @@ const getTargetPlacement = (
|
|
|
81
81
|
): TargetPlacement => {
|
|
82
82
|
const config = tree.getConfig();
|
|
83
83
|
|
|
84
|
-
if (!config.
|
|
84
|
+
if (!config.canReorder) {
|
|
85
85
|
return canMakeChild
|
|
86
86
|
? { type: PlacementType.MakeChild }
|
|
87
87
|
: { type: PlacementType.ReorderBelow };
|
|
@@ -156,7 +156,7 @@ export const getDropTarget = (
|
|
|
156
156
|
e: any,
|
|
157
157
|
item: ItemInstance<any>,
|
|
158
158
|
tree: TreeInstance<any>,
|
|
159
|
-
|
|
159
|
+
canReorder = tree.getConfig().canReorder,
|
|
160
160
|
): DropTarget<any> => {
|
|
161
161
|
const draggedItems = tree.getState().dnd?.draggedItems ?? [];
|
|
162
162
|
const itemMeta = item.getItemMeta();
|
|
@@ -184,7 +184,7 @@ export const getDropTarget = (
|
|
|
184
184
|
const placement = getTargetPlacement(e, item, tree, canMakeChild);
|
|
185
185
|
|
|
186
186
|
if (
|
|
187
|
-
!
|
|
187
|
+
!canReorder &&
|
|
188
188
|
parent &&
|
|
189
189
|
canBecomeSibling &&
|
|
190
190
|
placement.type !== PlacementType.MakeChild
|
|
@@ -192,7 +192,7 @@ export const getDropTarget = (
|
|
|
192
192
|
return parentTarget;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
if (!
|
|
195
|
+
if (!canReorder && parent && !canBecomeSibling) {
|
|
196
196
|
// TODO! this breaks in story DND/Can Drop. Maybe move this logic into a composable DropTargetStrategy[] ?
|
|
197
197
|
return getDropTarget(e, parent, tree, false);
|
|
198
198
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { describe, it } from "vitest";
|
|
2
2
|
import { TestTree } from "../../test-utils/test-tree";
|
|
3
3
|
import { expandAllFeature } from "./feature";
|
|
4
|
+
import { propMemoizationFeature } from "../prop-memoization/feature";
|
|
4
5
|
|
|
5
|
-
const factory = TestTree.default({}).withFeatures(
|
|
6
|
+
const factory = TestTree.default({}).withFeatures(
|
|
7
|
+
expandAllFeature,
|
|
8
|
+
propMemoizationFeature,
|
|
9
|
+
);
|
|
6
10
|
|
|
7
11
|
describe("core-feature/expand-all", () => {
|
|
8
12
|
factory.forSuits((tree) => {
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { FeatureImplementation } from "../../types/core";
|
|
2
|
-
import { ExpandAllFeatureDef } from "./types";
|
|
3
|
-
import { MainFeatureDef } from "../main/types";
|
|
4
|
-
import { TreeFeatureDef } from "../tree/types";
|
|
5
|
-
import { SyncDataLoaderFeatureDef } from "../sync-data-loader/types";
|
|
6
2
|
import { poll } from "../../utils";
|
|
7
3
|
|
|
8
|
-
export const expandAllFeature: FeatureImplementation
|
|
9
|
-
any,
|
|
10
|
-
ExpandAllFeatureDef,
|
|
11
|
-
| MainFeatureDef
|
|
12
|
-
| TreeFeatureDef<any>
|
|
13
|
-
| SyncDataLoaderFeatureDef<any>
|
|
14
|
-
| ExpandAllFeatureDef
|
|
15
|
-
> = {
|
|
4
|
+
export const expandAllFeature: FeatureImplementation = {
|
|
16
5
|
key: "expand-all",
|
|
17
6
|
|
|
18
7
|
treeInstance: {
|
|
@@ -3,12 +3,7 @@ import {
|
|
|
3
3
|
HotkeysConfig,
|
|
4
4
|
TreeInstance,
|
|
5
5
|
} from "../../types/core";
|
|
6
|
-
import {
|
|
7
|
-
HotkeyConfig,
|
|
8
|
-
HotkeysCoreDataRef,
|
|
9
|
-
HotkeysCoreFeatureDef,
|
|
10
|
-
} from "./types";
|
|
11
|
-
import { MainFeatureDef } from "../main/types";
|
|
6
|
+
import { HotkeyConfig, HotkeysCoreDataRef } from "./types";
|
|
12
7
|
|
|
13
8
|
const specialKeys: Record<string, RegExp> = {
|
|
14
9
|
Letter: /^[a-z]$/,
|
|
@@ -34,19 +29,15 @@ const testHotkeyMatch = (
|
|
|
34
29
|
const findHotkeyMatch = (
|
|
35
30
|
pressedKeys: Set<string>,
|
|
36
31
|
tree: TreeInstance<any>,
|
|
37
|
-
config1: HotkeysConfig<any
|
|
38
|
-
config2: HotkeysConfig<any
|
|
32
|
+
config1: HotkeysConfig<any>,
|
|
33
|
+
config2: HotkeysConfig<any>,
|
|
39
34
|
) => {
|
|
40
35
|
return Object.entries({ ...config1, ...config2 }).find(([, hotkey]) =>
|
|
41
36
|
testHotkeyMatch(pressedKeys, tree, hotkey),
|
|
42
37
|
)?.[0] as keyof HotkeysConfig<any> | undefined;
|
|
43
38
|
};
|
|
44
39
|
|
|
45
|
-
export const hotkeysCoreFeature: FeatureImplementation
|
|
46
|
-
any,
|
|
47
|
-
HotkeysCoreFeatureDef<any>,
|
|
48
|
-
MainFeatureDef | HotkeysCoreFeatureDef<any>
|
|
49
|
-
> = {
|
|
40
|
+
export const hotkeysCoreFeature: FeatureImplementation = {
|
|
50
41
|
key: "hotkeys-core",
|
|
51
42
|
|
|
52
43
|
onTreeMount: (tree, element) => {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { FeatureImplementation } from "../../types/core";
|
|
2
|
+
import { PropMemoizationDataRef } from "./types";
|
|
3
|
+
|
|
4
|
+
const memoize = (
|
|
5
|
+
props: Record<string, any>,
|
|
6
|
+
dataRef: PropMemoizationDataRef,
|
|
7
|
+
) => {
|
|
8
|
+
dataRef.memoizedProps ??= {};
|
|
9
|
+
for (const key in props) {
|
|
10
|
+
if (typeof props[key] === "function") {
|
|
11
|
+
if (key in dataRef.memoizedProps) {
|
|
12
|
+
props[key] = dataRef.memoizedProps[key];
|
|
13
|
+
} else {
|
|
14
|
+
dataRef.memoizedProps[key] = props[key];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return props;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const propMemoizationFeature: FeatureImplementation = {
|
|
22
|
+
key: "prop-memoization",
|
|
23
|
+
|
|
24
|
+
overwrites: [
|
|
25
|
+
"main",
|
|
26
|
+
"async-data-loader",
|
|
27
|
+
"sync-data-loader",
|
|
28
|
+
"drag-and-drop",
|
|
29
|
+
"expand-all",
|
|
30
|
+
"hotkeys-core",
|
|
31
|
+
"renaming",
|
|
32
|
+
"search",
|
|
33
|
+
"selection",
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
treeInstance: {
|
|
37
|
+
getContainerProps: ({ tree, prev }) => {
|
|
38
|
+
const dataRef = tree.getDataRef<PropMemoizationDataRef>();
|
|
39
|
+
const props = prev?.() ?? {};
|
|
40
|
+
return memoize(props, dataRef.current);
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
itemInstance: {
|
|
45
|
+
getProps: ({ item, prev }) => {
|
|
46
|
+
const dataRef = item.getDataRef<PropMemoizationDataRef>();
|
|
47
|
+
const props = prev?.() ?? {};
|
|
48
|
+
return memoize(props, dataRef.current);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { TestTree } from "../../test-utils/test-tree";
|
|
3
|
+
import { propMemoizationFeature } from "./feature";
|
|
4
|
+
import { FeatureImplementation } from "../../types/core";
|
|
5
|
+
|
|
6
|
+
const itemHandler = vi.fn();
|
|
7
|
+
const treeHandler = vi.fn();
|
|
8
|
+
const createItemValue = vi.fn();
|
|
9
|
+
const createTreeValue = vi.fn();
|
|
10
|
+
|
|
11
|
+
const customFeature: FeatureImplementation = {
|
|
12
|
+
itemInstance: {
|
|
13
|
+
getProps: ({ prev }) => ({
|
|
14
|
+
...prev?.(),
|
|
15
|
+
customValue: createItemValue(),
|
|
16
|
+
onCustomEvent: () => itemHandler(),
|
|
17
|
+
}),
|
|
18
|
+
},
|
|
19
|
+
treeInstance: {
|
|
20
|
+
getContainerProps: ({ prev }) => ({
|
|
21
|
+
...prev?.(),
|
|
22
|
+
customValue: createTreeValue(),
|
|
23
|
+
onCustomEvent: () => treeHandler(),
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const factory = TestTree.default({}).withFeatures(
|
|
29
|
+
customFeature,
|
|
30
|
+
propMemoizationFeature,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
describe("core-feature/prop-memoization", () => {
|
|
34
|
+
it("memoizes props", async () => {
|
|
35
|
+
const tree = await factory.suits.sync().tree.createTestCaseTree();
|
|
36
|
+
createTreeValue.mockReturnValue(123);
|
|
37
|
+
expect(tree.instance.getContainerProps().onCustomEvent).toBe(
|
|
38
|
+
tree.instance.getContainerProps().onCustomEvent,
|
|
39
|
+
);
|
|
40
|
+
expect(tree.instance.getContainerProps().customValue).toBe(123);
|
|
41
|
+
expect(tree.instance.getContainerProps().customValue).toBe(123);
|
|
42
|
+
});
|
|
43
|
+
factory.forSuits((tree) => {
|
|
44
|
+
describe("tree props", () => {
|
|
45
|
+
it("memoizes props", async () => {
|
|
46
|
+
createTreeValue.mockReturnValue(123);
|
|
47
|
+
expect(tree.instance.getContainerProps().onCustomEvent).toBe(
|
|
48
|
+
tree.instance.getContainerProps().onCustomEvent,
|
|
49
|
+
);
|
|
50
|
+
expect(tree.instance.getContainerProps().customValue).toBe(123);
|
|
51
|
+
expect(tree.instance.getContainerProps().customValue).toBe(123);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("doesnt return stale values", async () => {
|
|
55
|
+
createTreeValue.mockReturnValueOnce(123);
|
|
56
|
+
createTreeValue.mockReturnValueOnce(456);
|
|
57
|
+
expect(tree.instance.getContainerProps().customValue).toBe(123);
|
|
58
|
+
expect(tree.instance.getContainerProps().customValue).toBe(456);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("propagates calls properly", async () => {
|
|
62
|
+
tree.instance.getContainerProps().onCustomEvent();
|
|
63
|
+
tree.instance.getContainerProps().onCustomEvent();
|
|
64
|
+
expect(treeHandler).toHaveBeenCalledTimes(2);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { FeatureImplementation, ItemInstance } from "../../types/core";
|
|
2
|
-
import { RenamingFeatureDef } from "./types";
|
|
3
|
-
import { MainFeatureDef } from "../main/types";
|
|
4
|
-
import { TreeFeatureDef } from "../tree/types";
|
|
5
2
|
import { makeStateUpdater } from "../../utils";
|
|
6
3
|
|
|
7
|
-
export const renamingFeature: FeatureImplementation
|
|
8
|
-
any,
|
|
9
|
-
RenamingFeatureDef<any>,
|
|
10
|
-
MainFeatureDef | TreeFeatureDef<any> | RenamingFeatureDef<any>
|
|
11
|
-
> = {
|
|
4
|
+
export const renamingFeature: FeatureImplementation = {
|
|
12
5
|
key: "renaming",
|
|
13
6
|
|
|
14
7
|
getDefaultConfig: (defaultConfig, tree) => ({
|
|
@@ -24,17 +17,6 @@ export const renamingFeature: FeatureImplementation<
|
|
|
24
17
|
},
|
|
25
18
|
|
|
26
19
|
treeInstance: {
|
|
27
|
-
startRenamingItem: ({ tree }, itemId) => {
|
|
28
|
-
const item = tree.getItemInstance(itemId);
|
|
29
|
-
|
|
30
|
-
if (!item.canRename()) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
tree.applySubStateUpdate("renamingItem", itemId);
|
|
35
|
-
tree.applySubStateUpdate("renamingValue", item.getItemName());
|
|
36
|
-
},
|
|
37
|
-
|
|
38
20
|
getRenamingItem: ({ tree }) => {
|
|
39
21
|
const itemId = tree.getState().renamingItem;
|
|
40
22
|
return itemId ? tree.getItemInstance(itemId) : null;
|
|
@@ -61,6 +43,15 @@ export const renamingFeature: FeatureImplementation<
|
|
|
61
43
|
},
|
|
62
44
|
|
|
63
45
|
itemInstance: {
|
|
46
|
+
startRenaming: ({ tree, item, itemId }) => {
|
|
47
|
+
if (!item.canRename()) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
tree.applySubStateUpdate("renamingItem", itemId);
|
|
52
|
+
tree.applySubStateUpdate("renamingValue", item.getItemName());
|
|
53
|
+
},
|
|
54
|
+
|
|
64
55
|
getRenameInputProps: ({ tree }) => ({
|
|
65
56
|
onBlur: () => tree.abortRenaming(),
|
|
66
57
|
value: tree.getRenamingValue(),
|
|
@@ -81,7 +72,7 @@ export const renamingFeature: FeatureImplementation<
|
|
|
81
72
|
renameItem: {
|
|
82
73
|
hotkey: "F2",
|
|
83
74
|
handler: (e, tree) => {
|
|
84
|
-
tree.
|
|
75
|
+
tree.getFocusedItem().startRenaming();
|
|
85
76
|
},
|
|
86
77
|
},
|
|
87
78
|
|
|
@@ -2,16 +2,18 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { TestTree } from "../../test-utils/test-tree";
|
|
3
3
|
import { renamingFeature } from "./feature";
|
|
4
4
|
import { selectionFeature } from "../selection/feature";
|
|
5
|
+
import { propMemoizationFeature } from "../prop-memoization/feature";
|
|
5
6
|
|
|
6
7
|
const factory = TestTree.default({}).withFeatures(
|
|
7
8
|
renamingFeature,
|
|
8
9
|
selectionFeature,
|
|
10
|
+
propMemoizationFeature,
|
|
9
11
|
);
|
|
10
12
|
|
|
11
13
|
describe("core-feature/renaming", () => {
|
|
12
14
|
factory.forSuits((tree) => {
|
|
13
15
|
it("starts and aborts renaming", () => {
|
|
14
|
-
tree.
|
|
16
|
+
tree.item("x1").startRenaming();
|
|
15
17
|
expect(tree.instance.isRenamingItem()).toBe(true);
|
|
16
18
|
expect(tree.instance.getRenamingValue()).toBe("x1");
|
|
17
19
|
tree.instance.abortRenaming();
|
|
@@ -19,7 +21,7 @@ describe("core-feature/renaming", () => {
|
|
|
19
21
|
});
|
|
20
22
|
|
|
21
23
|
it("stops renaming by blurring", () => {
|
|
22
|
-
tree.
|
|
24
|
+
tree.item("x1").startRenaming();
|
|
23
25
|
tree.instance.getRenamingItem()!.getRenameInputProps().onBlur();
|
|
24
26
|
expect(tree.instance.isRenamingItem()).toBe(false);
|
|
25
27
|
});
|
|
@@ -27,7 +29,7 @@ describe("core-feature/renaming", () => {
|
|
|
27
29
|
it("completes renaming programmatically", () => {
|
|
28
30
|
const onRename = tree.mockedHandler("onRename");
|
|
29
31
|
|
|
30
|
-
tree.
|
|
32
|
+
tree.item("x1").startRenaming();
|
|
31
33
|
expect(tree.instance.getRenamingItem()!.getRenameInputProps().value).toBe(
|
|
32
34
|
"x1",
|
|
33
35
|
);
|
|
@@ -51,7 +53,7 @@ describe("core-feature/renaming", () => {
|
|
|
51
53
|
it("invokes state setters when aborting", () => {
|
|
52
54
|
const setRenamingItem = tree.mockedHandler("setRenamingItem");
|
|
53
55
|
const setRenamingValue = tree.mockedHandler("setRenamingValue");
|
|
54
|
-
tree.
|
|
56
|
+
tree.item("x1").startRenaming();
|
|
55
57
|
expect(setRenamingItem).toHaveBeenCalledWith("x1");
|
|
56
58
|
expect(setRenamingValue).toHaveBeenCalledWith("x1");
|
|
57
59
|
tree.instance.abortRenaming();
|
|
@@ -61,7 +63,7 @@ describe("core-feature/renaming", () => {
|
|
|
61
63
|
it("invokes state setters when completing", () => {
|
|
62
64
|
const setRenamingItem = tree.mockedHandler("setRenamingItem");
|
|
63
65
|
const setRenamingValue = tree.mockedHandler("setRenamingValue");
|
|
64
|
-
tree.
|
|
66
|
+
tree.item("x1").startRenaming();
|
|
65
67
|
tree.instance
|
|
66
68
|
.getRenamingItem()!
|
|
67
69
|
.getRenameInputProps()
|
|
@@ -76,7 +78,7 @@ describe("core-feature/renaming", () => {
|
|
|
76
78
|
|
|
77
79
|
it("changes renaming input content with input props", () => {
|
|
78
80
|
const setRenamingValue = tree.mockedHandler("setRenamingValue");
|
|
79
|
-
tree.
|
|
81
|
+
tree.item("x1").startRenaming();
|
|
80
82
|
tree.instance
|
|
81
83
|
.getRenamingItem()!
|
|
82
84
|
.getRenameInputProps()
|
|
@@ -86,7 +88,7 @@ describe("core-feature/renaming", () => {
|
|
|
86
88
|
|
|
87
89
|
it("aborts renaming with input props", () => {
|
|
88
90
|
const setRenamingItem = tree.mockedHandler("setRenamingItem");
|
|
89
|
-
tree.
|
|
91
|
+
tree.item("x1").startRenaming();
|
|
90
92
|
tree.instance.getRenamingItem()!.getRenameInputProps().onBlur();
|
|
91
93
|
expect(setRenamingItem).toHaveBeenCalledWith(null);
|
|
92
94
|
});
|
|
@@ -100,14 +102,14 @@ describe("core-feature/renaming", () => {
|
|
|
100
102
|
|
|
101
103
|
it("aborts renaming with Escape key", () => {
|
|
102
104
|
const setRenamingItem = tree.mockedHandler("setRenamingItem");
|
|
103
|
-
tree.
|
|
105
|
+
tree.item("x1").startRenaming();
|
|
104
106
|
tree.do.hotkey("abortRenaming");
|
|
105
107
|
expect(setRenamingItem).toHaveBeenCalledWith(null);
|
|
106
108
|
});
|
|
107
109
|
|
|
108
110
|
it("completes renaming with Enter key", () => {
|
|
109
111
|
const onRename = tree.mockedHandler("onRename");
|
|
110
|
-
tree.
|
|
112
|
+
tree.item("x1").startRenaming();
|
|
111
113
|
tree.instance
|
|
112
114
|
.getRenamingItem()!
|
|
113
115
|
.getRenameInputProps()
|