@headless-tree/core 1.0.0 → 1.1.0
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 +23 -0
- package/lib/cjs/features/async-data-loader/feature.js +78 -68
- package/lib/cjs/features/async-data-loader/types.d.ts +12 -7
- package/lib/cjs/features/drag-and-drop/feature.js +1 -0
- package/lib/cjs/features/expand-all/feature.js +2 -2
- package/lib/cjs/features/hotkeys-core/feature.js +50 -18
- package/lib/cjs/features/hotkeys-core/types.d.ts +3 -0
- package/lib/cjs/features/keyboard-drag-and-drop/feature.js +1 -1
- package/lib/cjs/features/selection/feature.js +3 -3
- package/lib/cjs/features/sync-data-loader/feature.js +13 -9
- package/lib/cjs/features/sync-data-loader/types.d.ts +11 -2
- package/lib/cjs/features/tree/feature.js +17 -8
- package/lib/cjs/test-utils/test-tree-expect.js +1 -0
- package/lib/cjs/utilities/errors.d.ts +1 -0
- package/lib/cjs/utilities/errors.js +6 -2
- package/lib/esm/features/async-data-loader/feature.js +78 -68
- package/lib/esm/features/async-data-loader/types.d.ts +12 -7
- package/lib/esm/features/drag-and-drop/feature.js +1 -0
- package/lib/esm/features/expand-all/feature.js +2 -2
- package/lib/esm/features/hotkeys-core/feature.js +50 -18
- package/lib/esm/features/hotkeys-core/types.d.ts +3 -0
- package/lib/esm/features/keyboard-drag-and-drop/feature.js +1 -1
- package/lib/esm/features/selection/feature.js +3 -3
- package/lib/esm/features/sync-data-loader/feature.js +13 -9
- package/lib/esm/features/sync-data-loader/types.d.ts +11 -2
- package/lib/esm/features/tree/feature.js +17 -8
- package/lib/esm/test-utils/test-tree-expect.js +1 -0
- package/lib/esm/utilities/errors.d.ts +1 -0
- package/lib/esm/utilities/errors.js +4 -1
- package/package.json +1 -1
- package/src/features/async-data-loader/async-data-loader.spec.ts +82 -0
- package/src/features/async-data-loader/feature.ts +92 -67
- package/src/features/async-data-loader/types.ts +14 -7
- package/src/features/drag-and-drop/feature.ts +1 -0
- package/src/features/expand-all/feature.ts +2 -2
- package/src/features/hotkeys-core/feature.ts +56 -17
- package/src/features/hotkeys-core/types.ts +4 -0
- package/src/features/keyboard-drag-and-drop/feature.ts +1 -1
- package/src/features/search/types.ts +1 -1
- package/src/features/selection/feature.ts +3 -3
- package/src/features/sync-data-loader/feature.ts +16 -9
- package/src/features/sync-data-loader/types.ts +11 -4
- package/src/features/tree/feature.ts +22 -8
- package/src/test-utils/test-tree-expect.ts +1 -0
- package/src/utilities/errors.ts +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @headless-tree/core
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 64d8e2a: add getChildrenWithData method to data loader to support fetching all children of an item at once
|
|
8
|
+
- 35260e3: fixed hotkey issues where releasing modifier keys (like shift) before normal keys can cause issues with subsequent keydown events
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 29b2c64: improved key-handling behavior for hotkeys while input elements are focused (#98)
|
|
13
|
+
- da1e757: fixed a bug where alt-tabbing out of browser will break hotkeys feature
|
|
14
|
+
- c283f52: add feature to allow async data invalidation without triggering rerenders with `invalidateItemData(optimistic: true)` (#95)
|
|
15
|
+
- 29b2c64: added option to completely ignore hotkey events while input elements are focused (`ignoreHotkeysOnInput`) (#98)
|
|
16
|
+
- cd5b27c: add position:absolute to default styles of getDragLineStyle()
|
|
17
|
+
|
|
18
|
+
## 1.0.1
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- c9f9932: fixed tree.focusNextItem() and tree.focusPreviousItem() throwing if no item is currently focused
|
|
23
|
+
- 6ed84b4: recursive item references are filtered out when rendering (#89)
|
|
24
|
+
- 4bef2f2: fixed a bug where hotkeys involving shift may not work properly depending on the order of shift and other key inputs (#98)
|
|
25
|
+
|
|
3
26
|
## 1.0.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
|
@@ -11,6 +11,51 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.asyncDataLoaderFeature = void 0;
|
|
13
13
|
const utils_1 = require("../../utils");
|
|
14
|
+
const getDataRef = (tree) => {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
var _c, _d;
|
|
17
|
+
const dataRef = tree.getDataRef();
|
|
18
|
+
(_a = (_c = dataRef.current).itemData) !== null && _a !== void 0 ? _a : (_c.itemData = {});
|
|
19
|
+
(_b = (_d = dataRef.current).childrenIds) !== null && _b !== void 0 ? _b : (_d.childrenIds = {});
|
|
20
|
+
return dataRef;
|
|
21
|
+
};
|
|
22
|
+
const loadItemData = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
var _a;
|
|
24
|
+
const config = tree.getConfig();
|
|
25
|
+
const dataRef = getDataRef(tree);
|
|
26
|
+
const item = yield config.dataLoader.getItem(itemId);
|
|
27
|
+
dataRef.current.itemData[itemId] = item;
|
|
28
|
+
(_a = config.onLoadedItem) === null || _a === void 0 ? void 0 : _a.call(config, itemId, item);
|
|
29
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId));
|
|
30
|
+
return item;
|
|
31
|
+
});
|
|
32
|
+
const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const config = tree.getConfig();
|
|
35
|
+
const dataRef = getDataRef(tree);
|
|
36
|
+
let childrenIds;
|
|
37
|
+
if ("getChildrenWithData" in config.dataLoader) {
|
|
38
|
+
const children = yield config.dataLoader.getChildrenWithData(itemId);
|
|
39
|
+
childrenIds = children.map((c) => c.id);
|
|
40
|
+
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
41
|
+
children.forEach(({ id, data }) => {
|
|
42
|
+
var _a;
|
|
43
|
+
dataRef.current.itemData[id] = data;
|
|
44
|
+
(_a = config.onLoadedItem) === null || _a === void 0 ? void 0 : _a.call(config, id, data);
|
|
45
|
+
});
|
|
46
|
+
(_a = config.onLoadedChildren) === null || _a === void 0 ? void 0 : _a.call(config, itemId, childrenIds);
|
|
47
|
+
tree.rebuildTree();
|
|
48
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => !childrenIds.includes(id)));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
childrenIds = yield config.dataLoader.getChildren(itemId);
|
|
52
|
+
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
53
|
+
(_b = config.onLoadedChildren) === null || _b === void 0 ? void 0 : _b.call(config, itemId, childrenIds);
|
|
54
|
+
tree.rebuildTree();
|
|
55
|
+
}
|
|
56
|
+
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId));
|
|
57
|
+
return childrenIds;
|
|
58
|
+
});
|
|
14
59
|
exports.asyncDataLoaderFeature = {
|
|
15
60
|
key: "async-data-loader",
|
|
16
61
|
getInitialState: (initialState) => (Object.assign({ loadingItemData: [], loadingItemChildrens: [] }, initialState)),
|
|
@@ -20,41 +65,20 @@ exports.asyncDataLoaderFeature = {
|
|
|
20
65
|
loadingItemChildrens: "setLoadingItemChildrens",
|
|
21
66
|
},
|
|
22
67
|
treeInstance: {
|
|
23
|
-
waitForItemDataLoaded: (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
yield new Promise((resolve) => {
|
|
29
|
-
var _a, _b;
|
|
30
|
-
var _c, _d;
|
|
31
|
-
const dataRef = tree.getDataRef();
|
|
32
|
-
(_a = (_c = dataRef.current).awaitingItemDataLoading) !== null && _a !== void 0 ? _a : (_c.awaitingItemDataLoading = {});
|
|
33
|
-
(_b = (_d = dataRef.current.awaitingItemDataLoading)[itemId]) !== null && _b !== void 0 ? _b : (_d[itemId] = []);
|
|
34
|
-
dataRef.current.awaitingItemDataLoading[itemId].push(resolve);
|
|
35
|
-
});
|
|
68
|
+
waitForItemDataLoaded: ({ tree }, itemId) => tree.loadItemData(itemId),
|
|
69
|
+
waitForItemChildrenLoaded: ({ tree }, itemId) => tree.loadChildrenIds(itemId),
|
|
70
|
+
loadItemData: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
71
|
+
var _b;
|
|
72
|
+
return ((_b = getDataRef(tree).current.itemData[itemId]) !== null && _b !== void 0 ? _b : (yield loadItemData(tree, itemId)));
|
|
36
73
|
}),
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
yield new Promise((resolve) => {
|
|
43
|
-
var _a, _b;
|
|
44
|
-
var _c, _d;
|
|
45
|
-
const dataRef = tree.getDataRef();
|
|
46
|
-
(_a = (_c = dataRef.current).awaitingItemChildrensLoading) !== null && _a !== void 0 ? _a : (_c.awaitingItemChildrensLoading = {});
|
|
47
|
-
(_b = (_d = dataRef.current.awaitingItemChildrensLoading)[itemId]) !== null && _b !== void 0 ? _b : (_d[itemId] = []);
|
|
48
|
-
dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
|
|
49
|
-
});
|
|
74
|
+
loadChildrenIds: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
|
|
75
|
+
var _b;
|
|
76
|
+
return ((_b = getDataRef(tree).current.childrenIds[itemId]) !== null && _b !== void 0 ? _b : (yield loadChildrenIds(tree, itemId)));
|
|
50
77
|
}),
|
|
51
78
|
retrieveItemData: ({ tree }, itemId) => {
|
|
52
|
-
var _a, _b
|
|
53
|
-
var _e, _f;
|
|
79
|
+
var _a, _b;
|
|
54
80
|
const config = tree.getConfig();
|
|
55
|
-
const dataRef =
|
|
56
|
-
(_a = (_e = dataRef.current).itemData) !== null && _a !== void 0 ? _a : (_e.itemData = {});
|
|
57
|
-
(_b = (_f = dataRef.current).childrenIds) !== null && _b !== void 0 ? _b : (_f.childrenIds = {});
|
|
81
|
+
const dataRef = getDataRef(tree);
|
|
58
82
|
if (dataRef.current.itemData[itemId]) {
|
|
59
83
|
return dataRef.current.itemData[itemId];
|
|
60
84
|
}
|
|
@@ -63,24 +87,12 @@ exports.asyncDataLoaderFeature = {
|
|
|
63
87
|
...loadingItemData,
|
|
64
88
|
itemId,
|
|
65
89
|
]);
|
|
66
|
-
(
|
|
67
|
-
var _a, _b, _c;
|
|
68
|
-
const item = yield config.dataLoader.getItem(itemId);
|
|
69
|
-
dataRef.current.itemData[itemId] = item;
|
|
70
|
-
(_a = config.onLoadedItem) === null || _a === void 0 ? void 0 : _a.call(config, itemId, item);
|
|
71
|
-
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId));
|
|
72
|
-
(_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[itemId].forEach((cb) => cb());
|
|
73
|
-
(_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[itemId];
|
|
74
|
-
}))();
|
|
90
|
+
loadItemData(tree, itemId);
|
|
75
91
|
}
|
|
76
|
-
return (
|
|
92
|
+
return (_b = (_a = config.createLoadingItemData) === null || _a === void 0 ? void 0 : _a.call(config)) !== null && _b !== void 0 ? _b : null;
|
|
77
93
|
},
|
|
78
94
|
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
79
|
-
|
|
80
|
-
var _b;
|
|
81
|
-
const config = tree.getConfig();
|
|
82
|
-
const dataRef = tree.getDataRef();
|
|
83
|
-
(_a = (_b = dataRef.current).childrenIds) !== null && _a !== void 0 ? _a : (_b.childrenIds = {});
|
|
95
|
+
const dataRef = getDataRef(tree);
|
|
84
96
|
if (dataRef.current.childrenIds[itemId]) {
|
|
85
97
|
return dataRef.current.childrenIds[itemId];
|
|
86
98
|
}
|
|
@@ -88,34 +100,32 @@ exports.asyncDataLoaderFeature = {
|
|
|
88
100
|
return [];
|
|
89
101
|
}
|
|
90
102
|
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [...loadingItemChildrens, itemId]);
|
|
91
|
-
(
|
|
92
|
-
var _a, _b, _c, _d;
|
|
93
|
-
const childrenIds = yield config.dataLoader.getChildren(itemId);
|
|
94
|
-
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
95
|
-
(_a = config.onLoadedChildren) === null || _a === void 0 ? void 0 : _a.call(config, itemId, childrenIds);
|
|
96
|
-
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId));
|
|
97
|
-
tree.rebuildTree();
|
|
98
|
-
(_c = (_b = dataRef.current.awaitingItemChildrensLoading) === null || _b === void 0 ? void 0 : _b[itemId]) === null || _c === void 0 ? void 0 : _c.forEach((cb) => cb());
|
|
99
|
-
(_d = dataRef.current.awaitingItemChildrensLoading) === null || _d === void 0 ? true : delete _d[itemId];
|
|
100
|
-
}))();
|
|
103
|
+
loadChildrenIds(tree, itemId);
|
|
101
104
|
return [];
|
|
102
105
|
},
|
|
103
106
|
},
|
|
104
107
|
itemInstance: {
|
|
105
108
|
isLoading: ({ tree, item }) => tree.getState().loadingItemData.includes(item.getItemMeta().itemId) ||
|
|
106
109
|
tree.getState().loadingItemChildrens.includes(item.getItemMeta().itemId),
|
|
107
|
-
invalidateItemData: ({ tree, itemId })
|
|
108
|
-
var
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
(
|
|
117
|
-
|
|
118
|
-
},
|
|
110
|
+
invalidateItemData: (_a, optimistic_1) => __awaiter(void 0, [_a, optimistic_1], void 0, function* ({ tree, itemId }, optimistic) {
|
|
111
|
+
var _b;
|
|
112
|
+
if (!optimistic) {
|
|
113
|
+
(_b = getDataRef(tree).current.itemData) === null || _b === void 0 ? true : delete _b[itemId];
|
|
114
|
+
tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
|
|
115
|
+
...loadingItemData,
|
|
116
|
+
itemId,
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
yield loadItemData(tree, itemId);
|
|
120
|
+
}),
|
|
121
|
+
invalidateChildrenIds: (_a, optimistic_1) => __awaiter(void 0, [_a, optimistic_1], void 0, function* ({ tree, itemId }, optimistic) {
|
|
122
|
+
var _b;
|
|
123
|
+
if (!optimistic) {
|
|
124
|
+
(_b = getDataRef(tree).current.childrenIds) === null || _b === void 0 ? true : delete _b[itemId];
|
|
125
|
+
tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [...loadingItemChildrens, itemId]);
|
|
126
|
+
}
|
|
127
|
+
yield loadChildrenIds(tree, itemId);
|
|
128
|
+
}),
|
|
119
129
|
updateCachedChildrenIds: ({ tree, itemId }, childrenIds) => {
|
|
120
130
|
const dataRef = tree.getDataRef();
|
|
121
131
|
dataRef.current.childrenIds[itemId] = childrenIds;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { SetStateFn } from "../../types/core";
|
|
2
2
|
import { SyncDataLoaderFeatureDef } from "../sync-data-loader/types";
|
|
3
|
-
type AwaitingLoaderCallbacks = Record<string, (() => void)[]>;
|
|
4
3
|
export interface AsyncDataLoaderDataRef<T = any> {
|
|
5
4
|
itemData: Record<string, T>;
|
|
6
5
|
childrenIds: Record<string, string[]>;
|
|
7
|
-
awaitingItemDataLoading: AwaitingLoaderCallbacks;
|
|
8
|
-
awaitingItemChildrensLoading: AwaitingLoaderCallbacks;
|
|
9
6
|
}
|
|
10
7
|
/**
|
|
11
8
|
* @category Async Data Loader/General
|
|
@@ -27,16 +24,24 @@ export type AsyncDataLoaderFeatureDef<T> = {
|
|
|
27
24
|
onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
|
|
28
25
|
};
|
|
29
26
|
treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
|
|
27
|
+
/** @deprecated use loadItemData instead */
|
|
30
28
|
waitForItemDataLoaded: (itemId: string) => Promise<void>;
|
|
29
|
+
/** @deprecated use loadChildrenIds instead */
|
|
31
30
|
waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
|
|
31
|
+
loadItemData: (itemId: string) => Promise<T>;
|
|
32
|
+
loadChildrenIds: (itemId: string) => Promise<string[]>;
|
|
32
33
|
};
|
|
33
34
|
itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
|
|
34
|
-
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
/** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
36
|
+
* @param optimistic If true, the item will not trigger a state update on `loadingItemData`, and
|
|
37
|
+
* the tree will continue to display the old data until the new data has loaded. */
|
|
38
|
+
invalidateItemData: (optimistic?: boolean) => Promise<void>;
|
|
39
|
+
/** Invalidate fetched children ids for item, and triggers a refetch and subsequent rerender if the item is visible
|
|
40
|
+
* @param optimistic If true, the item will not trigger a state update on `loadingItemChildrens`, and
|
|
41
|
+
* the tree will continue to display the old data until the new data has loaded. */
|
|
42
|
+
invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
|
|
37
43
|
updateCachedChildrenIds: (childrenIds: string[]) => void;
|
|
38
44
|
isLoading: () => boolean;
|
|
39
45
|
};
|
|
40
46
|
hotkeys: SyncDataLoaderFeatureDef<T>["hotkeys"];
|
|
41
47
|
};
|
|
42
|
-
export {};
|
|
@@ -51,7 +51,7 @@ exports.expandAllFeature = {
|
|
|
51
51
|
handler: (_, tree) => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
52
|
const cancelToken = { current: false };
|
|
53
53
|
const cancelHandler = (e) => {
|
|
54
|
-
if (e.
|
|
54
|
+
if (e.code === "Escape") {
|
|
55
55
|
cancelToken.current = true;
|
|
56
56
|
}
|
|
57
57
|
};
|
|
@@ -61,7 +61,7 @@ exports.expandAllFeature = {
|
|
|
61
61
|
}),
|
|
62
62
|
},
|
|
63
63
|
collapseSelected: {
|
|
64
|
-
hotkey: "Control+Shift
|
|
64
|
+
hotkey: "Control+Shift+Minus",
|
|
65
65
|
handler: (_, tree) => {
|
|
66
66
|
tree.getSelectedItems().forEach((item) => item.collapseAll());
|
|
67
67
|
},
|
|
@@ -2,16 +2,31 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.hotkeysCoreFeature = void 0;
|
|
4
4
|
const specialKeys = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// TODO:breaking deprecate auto-lowercase
|
|
6
|
+
letter: /^Key[A-Z]$/,
|
|
7
|
+
letterornumber: /^(Key[A-Z]|Digit[0-9])$/,
|
|
8
|
+
plus: /^(NumpadAdd|Plus)$/,
|
|
9
|
+
minus: /^(NumpadSubtract|Minus)$/,
|
|
10
|
+
control: /^(ControlLeft|ControlRight)$/,
|
|
11
|
+
shift: /^(ShiftLeft|ShiftRight)$/,
|
|
9
12
|
};
|
|
10
13
|
const testHotkeyMatch = (pressedKeys, tree, hotkey) => {
|
|
11
|
-
const supposedKeys = hotkey.hotkey.split("+");
|
|
12
|
-
const doKeysMatch = supposedKeys.every((key) =>
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
const supposedKeys = hotkey.hotkey.toLowerCase().split("+"); // TODO:breaking deprecate auto-lowercase
|
|
15
|
+
const doKeysMatch = supposedKeys.every((key) => {
|
|
16
|
+
if (key in specialKeys) {
|
|
17
|
+
return [...pressedKeys].some((pressedKey) => specialKeys[key].test(pressedKey));
|
|
18
|
+
}
|
|
19
|
+
const pressedKeysLowerCase = [...pressedKeys] // TODO:breaking deprecate auto-lowercase
|
|
20
|
+
.map((k) => k.toLowerCase());
|
|
21
|
+
if (pressedKeysLowerCase.includes(key.toLowerCase())) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (pressedKeysLowerCase.includes(`key${key.toLowerCase()}`)) {
|
|
25
|
+
// TODO:breaking deprecate e.key character matching
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
});
|
|
15
30
|
const isEnabled = !hotkey.isEnabled || hotkey.isEnabled(tree);
|
|
16
31
|
const equalCounts = pressedKeys.size === supposedKeys.length;
|
|
17
32
|
return doKeysMatch && isEnabled && equalCounts;
|
|
@@ -25,16 +40,24 @@ exports.hotkeysCoreFeature = {
|
|
|
25
40
|
onTreeMount: (tree, element) => {
|
|
26
41
|
const data = tree.getDataRef();
|
|
27
42
|
const keydown = (e) => {
|
|
28
|
-
var _a
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
var _a;
|
|
44
|
+
var _b;
|
|
45
|
+
const { ignoreHotkeysOnInputs, onTreeHotkey, hotkeys } = tree.getConfig();
|
|
46
|
+
if (e.target instanceof HTMLInputElement && ignoreHotkeysOnInputs) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
(_a = (_b = data.current).pressedKeys) !== null && _a !== void 0 ? _a : (_b.pressedKeys = new Set());
|
|
50
|
+
const newMatch = !data.current.pressedKeys.has(e.code);
|
|
51
|
+
data.current.pressedKeys.add(e.code);
|
|
52
|
+
const hotkeyName = findHotkeyMatch(data.current.pressedKeys, tree, tree.getHotkeyPresets(), hotkeys);
|
|
53
|
+
if (e.target instanceof HTMLInputElement) {
|
|
54
|
+
// JS respects composite keydowns while input elements are focused, and
|
|
55
|
+
// doesnt send the associated keyup events with the same key name
|
|
56
|
+
data.current.pressedKeys.delete(e.code);
|
|
57
|
+
}
|
|
35
58
|
if (!hotkeyName)
|
|
36
59
|
return;
|
|
37
|
-
const hotkeyConfig = Object.assign(Object.assign({}, tree.getHotkeyPresets()[hotkeyName]),
|
|
60
|
+
const hotkeyConfig = Object.assign(Object.assign({}, tree.getHotkeyPresets()[hotkeyName]), hotkeys === null || hotkeys === void 0 ? void 0 : hotkeys[hotkeyName]);
|
|
38
61
|
if (!hotkeyConfig)
|
|
39
62
|
return;
|
|
40
63
|
if (!hotkeyConfig.allowWhenInputFocused &&
|
|
@@ -45,21 +68,26 @@ exports.hotkeysCoreFeature = {
|
|
|
45
68
|
if (hotkeyConfig.preventDefault)
|
|
46
69
|
e.preventDefault();
|
|
47
70
|
hotkeyConfig.handler(e, tree);
|
|
48
|
-
|
|
71
|
+
onTreeHotkey === null || onTreeHotkey === void 0 ? void 0 : onTreeHotkey(hotkeyName, e);
|
|
49
72
|
};
|
|
50
73
|
const keyup = (e) => {
|
|
51
74
|
var _a;
|
|
52
75
|
var _b;
|
|
53
76
|
(_a = (_b = data.current).pressedKeys) !== null && _a !== void 0 ? _a : (_b.pressedKeys = new Set());
|
|
54
|
-
data.current.pressedKeys.delete(e.
|
|
77
|
+
data.current.pressedKeys.delete(e.code);
|
|
78
|
+
};
|
|
79
|
+
const reset = () => {
|
|
80
|
+
data.current.pressedKeys = new Set();
|
|
55
81
|
};
|
|
56
82
|
// keyup is registered on document, because some hotkeys shift
|
|
57
83
|
// the focus away from the tree (i.e. search)
|
|
58
84
|
// and then we wouldn't get the keyup event anymore
|
|
59
85
|
element.addEventListener("keydown", keydown);
|
|
60
86
|
document.addEventListener("keyup", keyup);
|
|
87
|
+
window.addEventListener("focus", reset);
|
|
61
88
|
data.current.keydownHandler = keydown;
|
|
62
89
|
data.current.keyupHandler = keyup;
|
|
90
|
+
data.current.resetHandler = reset;
|
|
63
91
|
},
|
|
64
92
|
onTreeUnmount: (tree, element) => {
|
|
65
93
|
const data = tree.getDataRef();
|
|
@@ -71,5 +99,9 @@ exports.hotkeysCoreFeature = {
|
|
|
71
99
|
element.removeEventListener("keydown", data.current.keydownHandler);
|
|
72
100
|
delete data.current.keydownHandler;
|
|
73
101
|
}
|
|
102
|
+
if (data.current.resetHandler) {
|
|
103
|
+
window.removeEventListener("focus", data.current.resetHandler);
|
|
104
|
+
delete data.current.resetHandler;
|
|
105
|
+
}
|
|
74
106
|
},
|
|
75
107
|
};
|
|
@@ -10,6 +10,7 @@ export interface HotkeyConfig<T> {
|
|
|
10
10
|
export interface HotkeysCoreDataRef {
|
|
11
11
|
keydownHandler?: (e: KeyboardEvent) => void;
|
|
12
12
|
keyupHandler?: (e: KeyboardEvent) => void;
|
|
13
|
+
resetHandler?: (e: FocusEvent) => void;
|
|
13
14
|
pressedKeys: Set<string>;
|
|
14
15
|
}
|
|
15
16
|
export type HotkeysCoreFeatureDef<T> = {
|
|
@@ -17,6 +18,8 @@ export type HotkeysCoreFeatureDef<T> = {
|
|
|
17
18
|
config: {
|
|
18
19
|
hotkeys?: CustomHotkeysConfig<T>;
|
|
19
20
|
onTreeHotkey?: (name: string, e: KeyboardEvent) => void;
|
|
21
|
+
/** Do not handle key inputs while an HTML input element is focused */
|
|
22
|
+
ignoreHotkeysOnInputs?: boolean;
|
|
20
23
|
};
|
|
21
24
|
treeInstance: {};
|
|
22
25
|
itemInstance: {};
|
|
@@ -28,9 +28,9 @@ exports.selectionFeature = {
|
|
|
28
28
|
const { selectedItems } = tree.getState();
|
|
29
29
|
tree.setSelectedItems(selectedItems.filter((id) => id !== itemId));
|
|
30
30
|
},
|
|
31
|
-
isSelected: ({ tree,
|
|
31
|
+
isSelected: ({ tree, itemId }) => {
|
|
32
32
|
const { selectedItems } = tree.getState();
|
|
33
|
-
return selectedItems.includes(
|
|
33
|
+
return selectedItems.includes(itemId);
|
|
34
34
|
},
|
|
35
35
|
selectUpTo: ({ tree, item }, ctrl) => {
|
|
36
36
|
const indexA = item.getItemMeta().index;
|
|
@@ -122,7 +122,7 @@ exports.selectionFeature = {
|
|
|
122
122
|
},
|
|
123
123
|
},
|
|
124
124
|
selectAll: {
|
|
125
|
-
hotkey: "Control+
|
|
125
|
+
hotkey: "Control+KeyA",
|
|
126
126
|
preventDefault: true,
|
|
127
127
|
handler: (e, tree) => {
|
|
128
128
|
tree.setSelectedItems(tree.getItems().map((item) => item.getId()));
|
|
@@ -13,6 +13,12 @@ exports.syncDataLoaderFeature = void 0;
|
|
|
13
13
|
const utils_1 = require("../../utils");
|
|
14
14
|
const errors_1 = require("../../utilities/errors");
|
|
15
15
|
const promiseErrorMessage = "sync dataLoader returned promise";
|
|
16
|
+
const unpromise = (data) => {
|
|
17
|
+
if (!data || (typeof data === "object" && "then" in data)) {
|
|
18
|
+
throw (0, errors_1.throwError)(promiseErrorMessage);
|
|
19
|
+
}
|
|
20
|
+
return data;
|
|
21
|
+
};
|
|
16
22
|
exports.syncDataLoaderFeature = {
|
|
17
23
|
key: "sync-data-loader",
|
|
18
24
|
getInitialState: (initialState) => (Object.assign({ loadingItemData: [], loadingItemChildrens: [] }, initialState)),
|
|
@@ -25,19 +31,17 @@ exports.syncDataLoaderFeature = {
|
|
|
25
31
|
waitForItemDataLoaded: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
26
32
|
waitForItemChildrenLoaded: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
27
33
|
retrieveItemData: ({ tree }, itemId) => {
|
|
28
|
-
|
|
29
|
-
if (typeof data === "object" && "then" in data) {
|
|
30
|
-
throw (0, errors_1.throwError)(promiseErrorMessage);
|
|
31
|
-
}
|
|
32
|
-
return data;
|
|
34
|
+
return unpromise(tree.getConfig().dataLoader.getItem(itemId));
|
|
33
35
|
},
|
|
34
36
|
retrieveChildrenIds: ({ tree }, itemId) => {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
|
|
37
|
+
const { dataLoader } = tree.getConfig();
|
|
38
|
+
if ("getChildren" in dataLoader) {
|
|
39
|
+
return unpromise(dataLoader.getChildren(itemId));
|
|
38
40
|
}
|
|
39
|
-
return data;
|
|
41
|
+
return unpromise(dataLoader.getChildrenWithData(itemId)).map((c) => c.data);
|
|
40
42
|
},
|
|
43
|
+
loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId),
|
|
44
|
+
loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId),
|
|
41
45
|
},
|
|
42
46
|
itemInstance: {
|
|
43
47
|
isLoading: () => false,
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type TreeDataLoader<T> = {
|
|
2
2
|
getItem: (itemId: string) => T | Promise<T>;
|
|
3
3
|
getChildren: (itemId: string) => string[] | Promise<string[]>;
|
|
4
|
-
}
|
|
4
|
+
} | {
|
|
5
|
+
getItem: (itemId: string) => T | Promise<T>;
|
|
6
|
+
getChildrenWithData: (itemId: string) => {
|
|
7
|
+
id: string;
|
|
8
|
+
data: T;
|
|
9
|
+
}[] | Promise<{
|
|
10
|
+
id: string;
|
|
11
|
+
data: T;
|
|
12
|
+
}[]>;
|
|
13
|
+
};
|
|
5
14
|
export type SyncDataLoaderFeatureDef<T> = {
|
|
6
15
|
state: {};
|
|
7
16
|
config: {
|
|
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.treeFeature = void 0;
|
|
13
13
|
const utils_1 = require("../../utils");
|
|
14
|
+
const errors_1 = require("../../utilities/errors");
|
|
14
15
|
exports.treeFeature = {
|
|
15
16
|
key: "tree",
|
|
16
17
|
getInitialState: (initialState) => (Object.assign({ expandedItems: [], focusedItem: null }, initialState)),
|
|
@@ -25,13 +26,17 @@ exports.treeFeature = {
|
|
|
25
26
|
const { expandedItems } = tree.getState();
|
|
26
27
|
const flatItems = [];
|
|
27
28
|
const expandedItemsSet = new Set(expandedItems); // TODO support setting state expandedItems as set instead of array
|
|
28
|
-
const recursiveAdd = (itemId,
|
|
29
|
+
const recursiveAdd = (itemId, path, level, setSize, posInSet) => {
|
|
29
30
|
var _a;
|
|
31
|
+
if (path.includes(itemId)) {
|
|
32
|
+
(0, errors_1.logWarning)(`Circular reference for ${path.join(".")}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
30
35
|
flatItems.push({
|
|
31
36
|
itemId,
|
|
32
37
|
level,
|
|
33
38
|
index: flatItems.length,
|
|
34
|
-
parentId,
|
|
39
|
+
parentId: path.at(-1),
|
|
35
40
|
setSize,
|
|
36
41
|
posInSet,
|
|
37
42
|
});
|
|
@@ -39,14 +44,14 @@ exports.treeFeature = {
|
|
|
39
44
|
const children = (_a = tree.retrieveChildrenIds(itemId)) !== null && _a !== void 0 ? _a : [];
|
|
40
45
|
let i = 0;
|
|
41
46
|
for (const childId of children) {
|
|
42
|
-
recursiveAdd(childId, itemId, level + 1, children.length, i++);
|
|
47
|
+
recursiveAdd(childId, path.concat(itemId), level + 1, children.length, i++);
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
};
|
|
46
51
|
const children = tree.retrieveChildrenIds(rootItemId);
|
|
47
52
|
let i = 0;
|
|
48
53
|
for (const itemId of children) {
|
|
49
|
-
recursiveAdd(itemId, rootItemId, 0, children.length, i++);
|
|
54
|
+
recursiveAdd(itemId, [rootItemId], 0, children.length, i++);
|
|
50
55
|
}
|
|
51
56
|
return flatItems;
|
|
52
57
|
},
|
|
@@ -56,14 +61,18 @@ exports.treeFeature = {
|
|
|
56
61
|
},
|
|
57
62
|
focusNextItem: ({ tree }) => {
|
|
58
63
|
var _a;
|
|
59
|
-
const
|
|
60
|
-
|
|
64
|
+
const focused = tree.getFocusedItem().getItemMeta();
|
|
65
|
+
if (!focused)
|
|
66
|
+
return;
|
|
67
|
+
const nextIndex = Math.min(focused.index + 1, tree.getItems().length - 1);
|
|
61
68
|
(_a = tree.getItems()[nextIndex]) === null || _a === void 0 ? void 0 : _a.setFocused();
|
|
62
69
|
},
|
|
63
70
|
focusPreviousItem: ({ tree }) => {
|
|
64
71
|
var _a;
|
|
65
|
-
const
|
|
66
|
-
|
|
72
|
+
const focused = tree.getFocusedItem().getItemMeta();
|
|
73
|
+
if (!focused)
|
|
74
|
+
return;
|
|
75
|
+
const nextIndex = Math.max(focused.index - 1, 0);
|
|
67
76
|
(_a = tree.getItems()[nextIndex]) === null || _a === void 0 ? void 0 : _a.setFocused();
|
|
68
77
|
},
|
|
69
78
|
updateDomFocus: ({ tree }) => {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.throwError = void 0;
|
|
4
|
-
const
|
|
3
|
+
exports.logWarning = exports.throwError = void 0;
|
|
4
|
+
const prefix = "Headless Tree: ";
|
|
5
|
+
const throwError = (message) => Error(prefix + message);
|
|
5
6
|
exports.throwError = throwError;
|
|
7
|
+
// eslint-disable-next-line no-console
|
|
8
|
+
const logWarning = (message) => console.warn(prefix + message);
|
|
9
|
+
exports.logWarning = logWarning;
|