@headless-tree/core 1.0.1 → 1.2.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/lib/cjs/core/create-tree.js +13 -2
  3. package/lib/cjs/features/async-data-loader/feature.js +78 -68
  4. package/lib/cjs/features/async-data-loader/types.d.ts +12 -7
  5. package/lib/cjs/features/drag-and-drop/feature.js +1 -0
  6. package/lib/cjs/features/expand-all/feature.js +2 -2
  7. package/lib/cjs/features/hotkeys-core/feature.js +50 -18
  8. package/lib/cjs/features/hotkeys-core/types.d.ts +3 -0
  9. package/lib/cjs/features/keyboard-drag-and-drop/feature.js +1 -1
  10. package/lib/cjs/features/renaming/feature.js +8 -0
  11. package/lib/cjs/features/selection/feature.js +1 -1
  12. package/lib/cjs/features/sync-data-loader/feature.js +13 -9
  13. package/lib/cjs/features/sync-data-loader/types.d.ts +11 -2
  14. package/lib/cjs/features/tree/feature.js +1 -0
  15. package/lib/cjs/features/tree/types.d.ts +1 -0
  16. package/lib/cjs/index.d.ts +2 -0
  17. package/lib/cjs/index.js +5 -0
  18. package/lib/cjs/test-utils/test-tree-expect.js +1 -0
  19. package/lib/cjs/test-utils/test-tree.js +1 -0
  20. package/lib/esm/core/create-tree.js +13 -2
  21. package/lib/esm/features/async-data-loader/feature.js +78 -68
  22. package/lib/esm/features/async-data-loader/types.d.ts +12 -7
  23. package/lib/esm/features/drag-and-drop/feature.js +1 -0
  24. package/lib/esm/features/expand-all/feature.js +2 -2
  25. package/lib/esm/features/hotkeys-core/feature.js +50 -18
  26. package/lib/esm/features/hotkeys-core/types.d.ts +3 -0
  27. package/lib/esm/features/keyboard-drag-and-drop/feature.js +1 -1
  28. package/lib/esm/features/renaming/feature.js +8 -0
  29. package/lib/esm/features/selection/feature.js +1 -1
  30. package/lib/esm/features/sync-data-loader/feature.js +13 -9
  31. package/lib/esm/features/sync-data-loader/types.d.ts +11 -2
  32. package/lib/esm/features/tree/feature.js +1 -0
  33. package/lib/esm/features/tree/types.d.ts +1 -0
  34. package/lib/esm/index.d.ts +2 -0
  35. package/lib/esm/index.js +2 -0
  36. package/lib/esm/test-utils/test-tree-expect.js +1 -0
  37. package/lib/esm/test-utils/test-tree.js +1 -0
  38. package/package.json +7 -2
  39. package/readme.md +2 -2
  40. package/src/core/create-tree.ts +20 -2
  41. package/src/features/async-data-loader/async-data-loader.spec.ts +82 -0
  42. package/src/features/async-data-loader/feature.ts +92 -67
  43. package/src/features/async-data-loader/types.ts +14 -7
  44. package/src/features/drag-and-drop/feature.ts +1 -0
  45. package/src/features/expand-all/feature.ts +2 -2
  46. package/src/features/hotkeys-core/feature.ts +56 -17
  47. package/src/features/hotkeys-core/types.ts +4 -0
  48. package/src/features/keyboard-drag-and-drop/feature.ts +1 -1
  49. package/src/features/renaming/feature.ts +13 -0
  50. package/src/features/renaming/renaming.spec.ts +31 -0
  51. package/src/features/selection/feature.ts +1 -1
  52. package/src/features/sync-data-loader/feature.ts +16 -9
  53. package/src/features/sync-data-loader/types.ts +11 -4
  54. package/src/features/tree/feature.ts +1 -0
  55. package/src/features/tree/types.ts +1 -0
  56. package/src/index.ts +3 -0
  57. package/src/test-utils/test-tree-expect.ts +1 -0
  58. package/src/test-utils/test-tree.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @headless-tree/core
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 647a072: Fixed incorrect package.json exports configurations to proper ESM and CJS exports (#104)
8
+ - 349d36e: change package.json["module"] to commonjs to fix inconsistent package definitiuons (#104)
9
+ - e2faf37: Fixed an issue async data loaders that resolve data before the tree is mounted can cause the tree to not render at all
10
+
11
+ Note: When using the `createTree()` API directly instead of going through the React `useTree` API, an additional call
12
+ to `tree.rebuildItems()` afterwards will be necessary. This change is marked as minor release regardless, since `createTree` is
13
+ currently not a publically documented feature.
14
+
15
+ ### Patch Changes
16
+
17
+ - 727c982: export makeStateUpdater from core package
18
+ - c041a3f: expose `isOrderedDragTarget` as utility method to differentiate between ordered and unordered drag targets (#108)
19
+ - 2887b0c: Added a `item.getKey()` utility method to use for generating React keys. For now, this just returns the item id, so no migration is needed from using `item.getId()` as React keys.
20
+ - 4e79bc7: Fixed a bug where `feature.overwrites` is not always respected when features are sorted during tree initialization
21
+ - 0669580: Fixed a bug where items that call `item.getProps` while being renamed, can be dragged while being renamed (#110)
22
+
23
+ ## 1.1.0
24
+
25
+ ### Minor Changes
26
+
27
+ - 64d8e2a: add getChildrenWithData method to data loader to support fetching all children of an item at once
28
+ - 35260e3: fixed hotkey issues where releasing modifier keys (like shift) before normal keys can cause issues with subsequent keydown events
29
+
30
+ ### Patch Changes
31
+
32
+ - 29b2c64: improved key-handling behavior for hotkeys while input elements are focused (#98)
33
+ - da1e757: fixed a bug where alt-tabbing out of browser will break hotkeys feature
34
+ - c283f52: add feature to allow async data invalidation without triggering rerenders with `invalidateItemData(optimistic: true)` (#95)
35
+ - 29b2c64: added option to completely ignore hotkey events while input elements are focused (`ignoreHotkeysOnInput`) (#98)
36
+ - cd5b27c: add position:absolute to default styles of getDragLineStyle()
37
+
3
38
  ## 1.0.1
4
39
 
5
40
  ### Patch Changes
@@ -14,6 +14,18 @@ const verifyFeatures = (features) => {
14
14
  }
15
15
  }
16
16
  };
17
+ // Check all possible pairs and sort the array
18
+ const exhaustiveSort = (arr, compareFn) => {
19
+ const n = arr.length;
20
+ for (let i = 0; i < n; i++) {
21
+ for (let j = i + 1; j < n; j++) {
22
+ if (compareFn(arr[j], arr[i]) < 0) {
23
+ [arr[i], arr[j]] = [arr[j], arr[i]];
24
+ }
25
+ }
26
+ }
27
+ return arr;
28
+ };
17
29
  const compareFeatures = (originalOrder) => (feature1, feature2) => {
18
30
  var _a, _b;
19
31
  if (feature2.key && ((_a = feature1.overwrites) === null || _a === void 0 ? void 0 : _a.includes(feature2.key))) {
@@ -24,7 +36,7 @@ const compareFeatures = (originalOrder) => (feature1, feature2) => {
24
36
  }
25
37
  return originalOrder.indexOf(feature1) - originalOrder.indexOf(feature2);
26
38
  };
27
- const sortFeatures = (features = []) => features.sort(compareFeatures(features));
39
+ const sortFeatures = (features = []) => exhaustiveSort(features, compareFeatures(features));
28
40
  const createTree = (initialConfig) => {
29
41
  var _a, _b, _c, _d;
30
42
  const buildInstance = (_a = initialConfig.instanceBuilder) !== null && _a !== void 0 ? _a : build_static_instance_1.buildStaticInstance;
@@ -165,7 +177,6 @@ const createTree = (initialConfig) => {
165
177
  Object.assign(hotkeyPresets, (_d = feature.hotkeys) !== null && _d !== void 0 ? _d : {});
166
178
  }
167
179
  finalizeTree();
168
- rebuildItemMeta();
169
180
  return treeInstance;
170
181
  };
171
182
  exports.createTree = createTree;
@@ -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: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
24
- tree.retrieveItemData(itemId);
25
- if (!tree.getState().loadingItemData.includes(itemId)) {
26
- return;
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
- waitForItemChildrenLoaded: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
38
- tree.retrieveChildrenIds(itemId);
39
- if (!tree.getState().loadingItemChildrens.includes(itemId)) {
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, _c, _d;
53
- var _e, _f;
79
+ var _a, _b;
54
80
  const config = tree.getConfig();
55
- const dataRef = tree.getDataRef();
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
- (() => __awaiter(void 0, void 0, void 0, function* () {
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 (_d = (_c = config.createLoadingItemData) === null || _c === void 0 ? void 0 : _c.call(config)) !== null && _d !== void 0 ? _d : null;
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
- var _a;
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
- (() => __awaiter(void 0, void 0, void 0, function* () {
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 _a;
109
- const dataRef = tree.getDataRef();
110
- (_a = dataRef.current.itemData) === null || _a === void 0 ? true : delete _a[itemId];
111
- tree.retrieveItemData(itemId);
112
- },
113
- invalidateChildrenIds: ({ tree, itemId }) => {
114
- var _a;
115
- const dataRef = tree.getDataRef();
116
- (_a = dataRef.current.childrenIds) === null || _a === void 0 ? true : delete _a[itemId];
117
- tree.retrieveChildrenIds(itemId);
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
- invalidateItemData: () => void;
36
- invalidateChildrenIds: () => void;
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 {};
@@ -60,6 +60,7 @@ exports.dragAndDropFeature = {
60
60
  const dragLine = tree.getDragLineData();
61
61
  return dragLine
62
62
  ? {
63
+ position: "absolute",
63
64
  top: `${dragLine.top + topOffset}px`,
64
65
  left: `${dragLine.left + leftOffset}px`,
65
66
  width: `${dragLine.width - leftOffset}px`,
@@ -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.key === "Escape") {
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
- Letter: /^[a-z]$/,
6
- LetterOrNumber: /^[a-z0-9]$/,
7
- Plus: /^\+$/,
8
- Space: /^ $/,
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.toLowerCase().split("+");
12
- const doKeysMatch = supposedKeys.every((key) => key in specialKeys
13
- ? [...pressedKeys].some((pressedKey) => specialKeys[key].test(pressedKey))
14
- : pressedKeys.has(key));
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, _b, _c, _d;
29
- var _e;
30
- const key = e.key.toLowerCase();
31
- (_a = (_e = data.current).pressedKeys) !== null && _a !== void 0 ? _a : (_e.pressedKeys = new Set());
32
- const newMatch = !data.current.pressedKeys.has(key);
33
- data.current.pressedKeys.add(key);
34
- const hotkeyName = findHotkeyMatch(data.current.pressedKeys, tree, tree.getHotkeyPresets(), tree.getConfig().hotkeys);
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]), (_b = tree.getConfig().hotkeys) === null || _b === void 0 ? void 0 : _b[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
- (_d = (_c = tree.getConfig()).onTreeHotkey) === null || _d === void 0 ? void 0 : _d.call(_c, hotkeyName, e);
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.key.toLowerCase());
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: {};
@@ -144,7 +144,7 @@ exports.keyboardDragAndDropFeature = {
144
144
  },
145
145
  hotkeys: {
146
146
  startDrag: {
147
- hotkey: "Control+Shift+D",
147
+ hotkey: "Control+Shift+KeyD",
148
148
  preventDefault: true,
149
149
  isEnabled: (tree) => !tree.getState().dnd,
150
150
  handler: (_, tree) => {
@@ -4,6 +4,7 @@ exports.renamingFeature = void 0;
4
4
  const utils_1 = require("../../utils");
5
5
  exports.renamingFeature = {
6
6
  key: "renaming",
7
+ overwrites: ["drag-and-drop"],
7
8
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setRenamingItem: (0, utils_1.makeStateUpdater)("renamingItem", tree), setRenamingValue: (0, utils_1.makeStateUpdater)("renamingValue", tree), canRename: () => true }, defaultConfig)),
8
9
  stateHandlerNames: {
9
10
  renamingItem: "setRenamingItem",
@@ -50,6 +51,13 @@ exports.renamingFeature = {
50
51
  }),
51
52
  canRename: ({ tree, item }) => { var _a, _b, _c; return (_c = (_b = (_a = tree.getConfig()).canRename) === null || _b === void 0 ? void 0 : _b.call(_a, item)) !== null && _c !== void 0 ? _c : true; },
52
53
  isRenaming: ({ tree, item }) => item.getId() === tree.getState().renamingItem,
54
+ getProps: ({ prev, item }) => {
55
+ var _a;
56
+ const isRenaming = item.isRenaming();
57
+ const prevProps = (_a = prev === null || prev === void 0 ? void 0 : prev()) !== null && _a !== void 0 ? _a : {};
58
+ return isRenaming
59
+ ? Object.assign(Object.assign({}, prevProps), { draggable: false, onDragStart: () => { } }) : prevProps;
60
+ },
53
61
  },
54
62
  hotkeys: {
55
63
  renameItem: {
@@ -122,7 +122,7 @@ exports.selectionFeature = {
122
122
  },
123
123
  },
124
124
  selectAll: {
125
- hotkey: "Control+a",
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
- const data = tree.getConfig().dataLoader.getItem(itemId);
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 data = tree.getConfig().dataLoader.getChildren(itemId);
36
- if (typeof data === "object" && "then" in data) {
37
- throw (0, errors_1.throwError)(promiseErrorMessage);
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 interface TreeDataLoader<T> {
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: {
@@ -100,6 +100,7 @@ exports.treeFeature = {
100
100
  (_d = item.getElement()) === null || _d === void 0 ? void 0 : _d.scrollIntoView(scrollIntoViewArg);
101
101
  }),
102
102
  getId: ({ itemId }) => itemId,
103
+ getKey: ({ itemId }) => itemId, // TODO apply to all stories to use
103
104
  getProps: ({ item, prev }) => {
104
105
  const itemMeta = item.getItemMeta();
105
106
  return Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { ref: item.registerElement, role: "treeitem", "aria-setsize": itemMeta.setSize, "aria-posinset": itemMeta.posInSet, "aria-selected": "false", "aria-label": item.getItemName(), "aria-level": itemMeta.level, tabIndex: item.isFocused() ? 0 : -1, onClick: (e) => {
@@ -37,6 +37,7 @@ export type TreeFeatureDef<T> = {
37
37
  };
38
38
  itemInstance: {
39
39
  getId: () => string;
40
+ getKey: () => string;
40
41
  getProps: () => Record<string, any>;
41
42
  getItemName: () => string;
42
43
  getItemData: () => T;
@@ -27,3 +27,5 @@ export * from "./utilities/insert-items-at-target";
27
27
  export * from "./utilities/remove-items-from-parents";
28
28
  export * from "./core/build-proxified-instance";
29
29
  export * from "./core/build-static-instance";
30
+ export { makeStateUpdater } from "./utils";
31
+ export { isOrderedDragTarget } from "./features/drag-and-drop/utils";
package/lib/cjs/index.js CHANGED
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.isOrderedDragTarget = exports.makeStateUpdater = void 0;
17
18
  __exportStar(require("./types/core"), exports);
18
19
  __exportStar(require("./core/create-tree"), exports);
19
20
  __exportStar(require("./features/tree/types"), exports);
@@ -42,3 +43,7 @@ __exportStar(require("./utilities/insert-items-at-target"), exports);
42
43
  __exportStar(require("./utilities/remove-items-from-parents"), exports);
43
44
  __exportStar(require("./core/build-proxified-instance"), exports);
44
45
  __exportStar(require("./core/build-static-instance"), exports);
46
+ var utils_1 = require("./utils");
47
+ Object.defineProperty(exports, "makeStateUpdater", { enumerable: true, get: function () { return utils_1.makeStateUpdater; } });
48
+ var utils_2 = require("./features/drag-and-drop/utils");
49
+ Object.defineProperty(exports, "isOrderedDragTarget", { enumerable: true, get: function () { return utils_2.isOrderedDragTarget; } });
@@ -55,6 +55,7 @@ class TestTreeExpect {
55
55
  top: 0,
56
56
  });
57
57
  (0, vitest_1.expect)(this.tree.instance.getDragLineStyle(0, 0)).toEqual({
58
+ position: "absolute",
58
59
  left: `${indent * 20}px`,
59
60
  pointerEvents: "none",
60
61
  top: "0px",
@@ -34,6 +34,7 @@ class TestTree {
34
34
  get instance() {
35
35
  if (!this.treeInstance) {
36
36
  this.treeInstance = (0, create_tree_1.createTree)(this.config);
37
+ this.treeInstance.rebuildTree();
37
38
  }
38
39
  return this.treeInstance;
39
40
  }