@headless-tree/core 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/cjs/core/build-proxified-instance.d.ts +2 -0
  3. package/lib/cjs/core/build-proxified-instance.js +58 -0
  4. package/lib/cjs/core/build-static-instance.d.ts +2 -0
  5. package/lib/cjs/core/build-static-instance.js +26 -0
  6. package/lib/cjs/core/create-tree.js +62 -40
  7. package/lib/cjs/features/async-data-loader/feature.d.ts +1 -4
  8. package/lib/cjs/features/async-data-loader/feature.js +35 -23
  9. package/lib/cjs/features/async-data-loader/types.d.ts +4 -6
  10. package/lib/cjs/features/drag-and-drop/feature.d.ts +2 -3
  11. package/lib/cjs/features/drag-and-drop/feature.js +79 -44
  12. package/lib/cjs/features/drag-and-drop/types.d.ts +15 -6
  13. package/lib/cjs/features/drag-and-drop/utils.d.ts +2 -3
  14. package/lib/cjs/features/drag-and-drop/utils.js +140 -37
  15. package/lib/cjs/features/expand-all/feature.d.ts +1 -5
  16. package/lib/cjs/features/expand-all/feature.js +12 -6
  17. package/lib/cjs/features/hotkeys-core/feature.d.ts +1 -3
  18. package/lib/cjs/features/main/types.d.ts +8 -2
  19. package/lib/cjs/features/prop-memoization/feature.d.ts +2 -0
  20. package/lib/cjs/features/prop-memoization/feature.js +48 -0
  21. package/lib/cjs/features/prop-memoization/types.d.ts +10 -0
  22. package/lib/cjs/features/prop-memoization/types.js +2 -0
  23. package/lib/cjs/features/renaming/feature.d.ts +1 -4
  24. package/lib/cjs/features/renaming/feature.js +36 -22
  25. package/lib/cjs/features/renaming/types.d.ts +2 -2
  26. package/lib/cjs/features/search/feature.d.ts +1 -4
  27. package/lib/cjs/features/search/feature.js +38 -24
  28. package/lib/cjs/features/search/types.d.ts +0 -1
  29. package/lib/cjs/features/selection/feature.d.ts +1 -4
  30. package/lib/cjs/features/selection/feature.js +54 -35
  31. package/lib/cjs/features/selection/types.d.ts +1 -1
  32. package/lib/cjs/features/sync-data-loader/feature.d.ts +1 -3
  33. package/lib/cjs/features/sync-data-loader/feature.js +7 -2
  34. package/lib/cjs/features/tree/feature.d.ts +1 -5
  35. package/lib/cjs/features/tree/feature.js +97 -92
  36. package/lib/cjs/features/tree/types.d.ts +5 -8
  37. package/lib/cjs/index.d.ts +5 -1
  38. package/lib/cjs/index.js +4 -1
  39. package/lib/cjs/mddocs-entry.d.ts +10 -0
  40. package/lib/cjs/test-utils/test-tree-do.d.ts +23 -0
  41. package/lib/cjs/test-utils/test-tree-do.js +99 -0
  42. package/lib/cjs/test-utils/test-tree-expect.d.ts +15 -0
  43. package/lib/cjs/test-utils/test-tree-expect.js +62 -0
  44. package/lib/cjs/test-utils/test-tree.d.ts +47 -0
  45. package/lib/cjs/test-utils/test-tree.js +203 -0
  46. package/lib/cjs/types/core.d.ts +39 -24
  47. package/lib/cjs/utilities/errors.d.ts +1 -0
  48. package/lib/cjs/utilities/errors.js +5 -0
  49. package/lib/cjs/utilities/insert-items-at-target.js +10 -3
  50. package/lib/cjs/utilities/remove-items-from-parents.js +14 -8
  51. package/lib/cjs/utils.d.ts +3 -3
  52. package/lib/cjs/utils.js +6 -6
  53. package/lib/esm/core/build-proxified-instance.d.ts +2 -0
  54. package/lib/esm/core/build-proxified-instance.js +54 -0
  55. package/lib/esm/core/build-static-instance.d.ts +2 -0
  56. package/lib/esm/core/build-static-instance.js +22 -0
  57. package/lib/esm/core/create-tree.js +62 -40
  58. package/lib/esm/features/async-data-loader/feature.d.ts +1 -4
  59. package/lib/esm/features/async-data-loader/feature.js +35 -23
  60. package/lib/esm/features/async-data-loader/types.d.ts +4 -6
  61. package/lib/esm/features/drag-and-drop/feature.d.ts +2 -3
  62. package/lib/esm/features/drag-and-drop/feature.js +79 -44
  63. package/lib/esm/features/drag-and-drop/types.d.ts +15 -6
  64. package/lib/esm/features/drag-and-drop/utils.d.ts +2 -3
  65. package/lib/esm/features/drag-and-drop/utils.js +138 -34
  66. package/lib/esm/features/expand-all/feature.d.ts +1 -5
  67. package/lib/esm/features/expand-all/feature.js +12 -6
  68. package/lib/esm/features/hotkeys-core/feature.d.ts +1 -3
  69. package/lib/esm/features/main/types.d.ts +8 -2
  70. package/lib/esm/features/prop-memoization/feature.d.ts +2 -0
  71. package/lib/esm/features/prop-memoization/feature.js +45 -0
  72. package/lib/esm/features/prop-memoization/types.d.ts +10 -0
  73. package/lib/esm/features/prop-memoization/types.js +1 -0
  74. package/lib/esm/features/renaming/feature.d.ts +1 -4
  75. package/lib/esm/features/renaming/feature.js +36 -22
  76. package/lib/esm/features/renaming/types.d.ts +2 -2
  77. package/lib/esm/features/search/feature.d.ts +1 -4
  78. package/lib/esm/features/search/feature.js +38 -24
  79. package/lib/esm/features/search/types.d.ts +0 -1
  80. package/lib/esm/features/selection/feature.d.ts +1 -4
  81. package/lib/esm/features/selection/feature.js +54 -35
  82. package/lib/esm/features/selection/types.d.ts +1 -1
  83. package/lib/esm/features/sync-data-loader/feature.d.ts +1 -3
  84. package/lib/esm/features/sync-data-loader/feature.js +7 -2
  85. package/lib/esm/features/tree/feature.d.ts +1 -5
  86. package/lib/esm/features/tree/feature.js +98 -93
  87. package/lib/esm/features/tree/types.d.ts +5 -8
  88. package/lib/esm/index.d.ts +5 -1
  89. package/lib/esm/index.js +4 -1
  90. package/lib/esm/mddocs-entry.d.ts +10 -0
  91. package/lib/esm/test-utils/test-tree-do.d.ts +23 -0
  92. package/lib/esm/test-utils/test-tree-do.js +95 -0
  93. package/lib/esm/test-utils/test-tree-expect.d.ts +15 -0
  94. package/lib/esm/test-utils/test-tree-expect.js +58 -0
  95. package/lib/esm/test-utils/test-tree.d.ts +47 -0
  96. package/lib/esm/test-utils/test-tree.js +199 -0
  97. package/lib/esm/types/core.d.ts +39 -24
  98. package/lib/esm/utilities/errors.d.ts +1 -0
  99. package/lib/esm/utilities/errors.js +1 -0
  100. package/lib/esm/utilities/insert-items-at-target.js +10 -3
  101. package/lib/esm/utilities/remove-items-from-parents.js +14 -8
  102. package/lib/esm/utils.d.ts +3 -3
  103. package/lib/esm/utils.js +3 -3
  104. package/package.json +7 -3
  105. package/src/core/build-proxified-instance.ts +117 -0
  106. package/src/core/build-static-instance.ts +27 -0
  107. package/src/core/core.spec.ts +210 -0
  108. package/src/core/create-tree.ts +73 -78
  109. package/src/features/async-data-loader/async-data-loader.spec.ts +124 -0
  110. package/src/features/async-data-loader/feature.ts +34 -44
  111. package/src/features/async-data-loader/types.ts +4 -6
  112. package/src/features/drag-and-drop/drag-and-drop.spec.ts +717 -0
  113. package/src/features/drag-and-drop/feature.ts +88 -63
  114. package/src/features/drag-and-drop/types.ts +24 -10
  115. package/src/features/drag-and-drop/utils.ts +197 -56
  116. package/src/features/expand-all/expand-all.spec.ts +56 -0
  117. package/src/features/expand-all/feature.ts +9 -24
  118. package/src/features/hotkeys-core/feature.ts +5 -14
  119. package/src/features/main/types.ts +14 -1
  120. package/src/features/prop-memoization/feature.ts +51 -0
  121. package/src/features/prop-memoization/prop-memoization.spec.ts +68 -0
  122. package/src/features/prop-memoization/types.ts +11 -0
  123. package/src/features/renaming/feature.ts +37 -45
  124. package/src/features/renaming/renaming.spec.ts +127 -0
  125. package/src/features/renaming/types.ts +2 -2
  126. package/src/features/search/feature.ts +36 -46
  127. package/src/features/search/search.spec.ts +117 -0
  128. package/src/features/search/types.ts +0 -1
  129. package/src/features/selection/feature.ts +50 -53
  130. package/src/features/selection/selection.spec.ts +219 -0
  131. package/src/features/selection/types.ts +0 -2
  132. package/src/features/sync-data-loader/feature.ts +9 -18
  133. package/src/features/tree/feature.ts +101 -144
  134. package/src/features/tree/tree.spec.ts +475 -0
  135. package/src/features/tree/types.ts +5 -9
  136. package/src/index.ts +6 -1
  137. package/src/mddocs-entry.ts +13 -0
  138. package/src/test-utils/test-tree-do.ts +136 -0
  139. package/src/test-utils/test-tree-expect.ts +86 -0
  140. package/src/test-utils/test-tree.ts +227 -0
  141. package/src/types/core.ts +76 -108
  142. package/src/utilities/errors.ts +2 -0
  143. package/src/utilities/insert-items-at-target.ts +10 -3
  144. package/src/utilities/remove-items-from-parents.ts +15 -10
  145. package/src/utils.spec.ts +89 -0
  146. package/src/utils.ts +6 -6
  147. package/tsconfig.json +1 -0
  148. package/vitest.config.ts +6 -0
@@ -6,25 +6,31 @@ export const selectionFeature = {
6
6
  stateHandlerNames: {
7
7
  selectedItems: "setSelectedItems",
8
8
  },
9
- createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { setSelectedItems: (selectedItems) => {
10
- instance.applySubStateUpdate("selectedItems", selectedItems);
11
- },
9
+ treeInstance: {
10
+ setSelectedItems: ({ tree }, selectedItems) => {
11
+ tree.applySubStateUpdate("selectedItems", selectedItems);
12
+ },
12
13
  // TODO memo
13
- getSelectedItems: () => {
14
- return instance.getState().selectedItems.map(instance.getItemInstance);
15
- } })),
16
- createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { select: () => {
14
+ getSelectedItems: ({ tree }) => {
15
+ return tree.getState().selectedItems.map(tree.getItemInstance);
16
+ },
17
+ },
18
+ itemInstance: {
19
+ select: ({ tree, itemId }) => {
17
20
  const { selectedItems } = tree.getState();
18
- tree.setSelectedItems(selectedItems.includes(item.getItemMeta().itemId)
21
+ tree.setSelectedItems(selectedItems.includes(itemId)
19
22
  ? selectedItems
20
- : [...selectedItems, item.getItemMeta().itemId]);
21
- }, deselect: () => {
23
+ : [...selectedItems, itemId]);
24
+ },
25
+ deselect: ({ tree, itemId }) => {
22
26
  const { selectedItems } = tree.getState();
23
- tree.setSelectedItems(selectedItems.filter((id) => id !== item.getItemMeta().itemId));
24
- }, isSelected: () => {
27
+ tree.setSelectedItems(selectedItems.filter((id) => id !== itemId));
28
+ },
29
+ isSelected: ({ tree, item }) => {
25
30
  const { selectedItems } = tree.getState();
26
31
  return selectedItems.includes(item.getItemMeta().itemId);
27
- }, selectUpTo: (ctrl) => {
32
+ },
33
+ selectUpTo: ({ tree, item }, ctrl) => {
28
34
  const indexA = item.getItemMeta().index;
29
35
  // TODO dont use focused item as anchor, but last primary-clicked item
30
36
  const indexB = tree.getFocusedItem().getItemMeta().index;
@@ -42,14 +48,16 @@ export const selectionFeature = {
42
48
  ...new Set([...selectedItems, ...newSelectedItems]),
43
49
  ];
44
50
  tree.setSelectedItems(uniqueSelectedItems);
45
- }, toggleSelect: () => {
51
+ },
52
+ toggleSelect: ({ item }) => {
46
53
  if (item.isSelected()) {
47
54
  item.deselect();
48
55
  }
49
56
  else {
50
57
  item.select();
51
58
  }
52
- }, getProps: () => (Object.assign(Object.assign({}, prev.getProps()), { "aria-selected": item.isSelected() ? "true" : "false", onClick: item.getMemoizedProp("selection/onClick", () => (e) => {
59
+ },
60
+ getProps: ({ tree, item, prev }) => (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { "aria-selected": item.isSelected() ? "true" : "false", onClick: (e) => {
53
61
  var _a, _b;
54
62
  if (e.shiftKey) {
55
63
  item.selectUpTo(e.ctrlKey || e.metaKey);
@@ -60,8 +68,9 @@ export const selectionFeature = {
60
68
  else {
61
69
  tree.setSelectedItems([item.getItemMeta().itemId]);
62
70
  }
63
- (_b = (_a = prev.getProps()).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
64
- }) })) })),
71
+ (_b = (_a = prev === null || prev === void 0 ? void 0 : prev()) === null || _a === void 0 ? void 0 : _a.onClick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
72
+ } })),
73
+ },
65
74
  hotkeys: {
66
75
  // setSelectedItem: {
67
76
  // hotkey: "space",
@@ -76,27 +85,37 @@ export const selectionFeature = {
76
85
  },
77
86
  },
78
87
  selectUpwards: {
79
- hotkey: "shift+ArrowUp",
80
- handler: () => {
81
- // TODO
88
+ hotkey: "Shift+ArrowUp",
89
+ handler: (e, tree) => {
90
+ const focused = tree.getFocusedItem();
91
+ const above = focused.getItemAbove();
92
+ if (!above)
93
+ return;
94
+ if (focused.isSelected() && above.isSelected()) {
95
+ focused.deselect();
96
+ }
97
+ else {
98
+ above.select();
99
+ }
100
+ above.setFocused();
101
+ tree.updateDomFocus();
82
102
  },
83
103
  },
84
104
  selectDownwards: {
85
- hotkey: "shift+ArrowDown",
86
- handler: () => {
87
- // TODO
88
- },
89
- },
90
- selectUpwardsCtrl: {
91
- hotkey: "shift+ctrl+ArrowUp",
92
- handler: () => {
93
- // TODO
94
- },
95
- },
96
- selectDownwardsCtrl: {
97
- hotkey: "shift+ctrl+ArrowUp",
98
- handler: () => {
99
- // TODO
105
+ hotkey: "Shift+ArrowDown",
106
+ handler: (e, tree) => {
107
+ const focused = tree.getFocusedItem();
108
+ const below = focused.getItemBelow();
109
+ if (!below)
110
+ return;
111
+ if (focused.isSelected() && below.isSelected()) {
112
+ focused.deselect();
113
+ }
114
+ else {
115
+ below.select();
116
+ }
117
+ below.setFocused();
118
+ tree.updateDomFocus();
100
119
  },
101
120
  },
102
121
  selectAll: {
@@ -17,5 +17,5 @@ export type SelectionFeatureDef<T> = {
17
17
  isSelected: () => boolean;
18
18
  selectUpTo: (ctrl: boolean) => void;
19
19
  };
20
- hotkeys: "toggleSelectItem" | "selectUpwards" | "selectDownwards" | "selectUpwardsCtrl" | "selectDownwardsCtrl" | "selectAll";
20
+ hotkeys: "toggleSelectItem" | "selectUpwards" | "selectDownwards" | "selectAll";
21
21
  };
@@ -1,4 +1,2 @@
1
1
  import { FeatureImplementation } from "../../types/core";
2
- import { SyncDataLoaderFeatureDef } from "./types";
3
- import { MainFeatureDef } from "../main/types";
4
- export declare const syncDataLoaderFeature: FeatureImplementation<any, SyncDataLoaderFeatureDef<any>, MainFeatureDef | SyncDataLoaderFeatureDef<any>>;
2
+ export declare const syncDataLoaderFeature: FeatureImplementation;
@@ -6,6 +6,11 @@ export const syncDataLoaderFeature = {
6
6
  stateHandlerNames: {
7
7
  loadingItems: "setLoadingItems",
8
8
  },
9
- createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: (itemId) => instance.getConfig().dataLoader.getItem(itemId), retrieveChildrenIds: (itemId) => instance.getConfig().dataLoader.getChildren(itemId) })),
10
- createItemInstance: (prev) => (Object.assign(Object.assign({}, prev), { isLoading: () => false })),
9
+ treeInstance: {
10
+ retrieveItemData: ({ tree }, itemId) => tree.getConfig().dataLoader.getItem(itemId),
11
+ retrieveChildrenIds: ({ tree }, itemId) => tree.getConfig().dataLoader.getChildren(itemId),
12
+ },
13
+ itemInstance: {
14
+ isLoading: () => false,
15
+ },
11
16
  };
@@ -1,6 +1,2 @@
1
1
  import { FeatureImplementation } from "../../types/core";
2
- import { TreeFeatureDef } from "./types";
3
- import { MainFeatureDef } from "../main/types";
4
- import { HotkeysCoreFeatureDef } from "../hotkeys-core/types";
5
- import { SyncDataLoaderFeatureDef } from "../sync-data-loader/types";
6
- export declare const treeFeature: FeatureImplementation<any, TreeFeatureDef<any>, MainFeatureDef | TreeFeatureDef<any> | HotkeysCoreFeatureDef<any> | SyncDataLoaderFeatureDef<any>>;
2
+ export declare const treeFeature: FeatureImplementation<any>;
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { makeStateUpdater, memo, poll } from "../../utils";
10
+ import { makeStateUpdater, poll } from "../../utils";
11
11
  export const treeFeature = {
12
12
  key: "tree",
13
13
  getInitialState: (initialState) => (Object.assign({ expandedItems: [], focusedItem: null }, initialState)),
@@ -16,14 +16,12 @@ export const treeFeature = {
16
16
  expandedItems: "setExpandedItems",
17
17
  focusedItem: "setFocusedItem",
18
18
  },
19
- createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: () => {
20
- throw new Error("No data-loader registered");
21
- }, retrieveChildrenIds: () => {
22
- throw new Error("No data-loader registered");
23
- }, isItemExpanded: (itemId) => instance.getState().expandedItems.includes(itemId), getItemsMeta: () => {
24
- const { rootItemId } = instance.getConfig();
25
- const { expandedItems } = instance.getState();
19
+ treeInstance: {
20
+ getItemsMeta: ({ tree }) => {
21
+ const { rootItemId } = tree.getConfig();
22
+ const { expandedItems } = tree.getState();
26
23
  const flatItems = [];
24
+ const expandedItemsSet = new Set(expandedItems);
27
25
  const recursiveAdd = (itemId, parentId, level, setSize, posInSet) => {
28
26
  var _a;
29
27
  flatItems.push({
@@ -34,81 +32,68 @@ export const treeFeature = {
34
32
  setSize,
35
33
  posInSet,
36
34
  });
37
- if (expandedItems.includes(itemId)) {
38
- const children = (_a = instance.retrieveChildrenIds(itemId)) !== null && _a !== void 0 ? _a : [];
35
+ if (expandedItemsSet.has(itemId)) {
36
+ // TODO THIS MADE A HUGE DIFFERENCE!
37
+ const children = (_a = tree.retrieveChildrenIds(itemId)) !== null && _a !== void 0 ? _a : [];
39
38
  let i = 0;
40
39
  for (const childId of children) {
41
40
  recursiveAdd(childId, itemId, level + 1, children.length, i++);
42
41
  }
43
42
  }
44
43
  };
45
- const children = instance.retrieveChildrenIds(rootItemId);
44
+ const children = tree.retrieveChildrenIds(rootItemId);
46
45
  let i = 0;
47
46
  for (const itemId of children) {
48
47
  recursiveAdd(itemId, rootItemId, 0, children.length, i++);
49
48
  }
50
49
  return flatItems;
51
- }, expandItem: (itemId) => {
52
- var _a;
53
- if (!instance.getItemInstance(itemId).isFolder()) {
54
- return;
55
- }
56
- if ((_a = instance.getState().loadingItems) === null || _a === void 0 ? void 0 : _a.includes(itemId)) {
57
- return;
58
- }
59
- instance.applySubStateUpdate("expandedItems", (expandedItems) => [
60
- ...expandedItems,
61
- itemId,
62
- ]);
63
- instance.rebuildTree();
64
- }, collapseItem: (itemId) => {
65
- if (!instance.getItemInstance(itemId).isFolder()) {
66
- return;
67
- }
68
- instance.applySubStateUpdate("expandedItems", (expandedItems) => expandedItems.filter((id) => id !== itemId));
69
- instance.rebuildTree();
70
- },
50
+ },
71
51
  // TODO memo
72
- getFocusedItem: () => {
52
+ getFocusedItem: ({ tree }) => {
73
53
  var _a, _b;
74
- return ((_b = instance.getItemInstance((_a = instance.getState().focusedItem) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : instance.getItems()[0]);
75
- }, focusItem: (itemId) => {
76
- instance.applySubStateUpdate("focusedItem", itemId);
77
- }, focusNextItem: () => {
78
- const { index } = instance.getFocusedItem().getItemMeta();
79
- const nextIndex = Math.min(index + 1, instance.getItems().length - 1);
80
- instance.focusItem(instance.getItems()[nextIndex].getId());
81
- }, focusPreviousItem: () => {
82
- const { index } = instance.getFocusedItem().getItemMeta();
54
+ return ((_b = tree.getItemInstance((_a = tree.getState().focusedItem) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : tree.getItems()[0]);
55
+ },
56
+ focusNextItem: ({ tree }) => {
57
+ var _a;
58
+ const { index } = tree.getFocusedItem().getItemMeta();
59
+ const nextIndex = Math.min(index + 1, tree.getItems().length - 1);
60
+ (_a = tree.getItems()[nextIndex]) === null || _a === void 0 ? void 0 : _a.setFocused();
61
+ },
62
+ focusPreviousItem: ({ tree }) => {
63
+ var _a;
64
+ const { index } = tree.getFocusedItem().getItemMeta();
83
65
  const nextIndex = Math.max(index - 1, 0);
84
- instance.focusItem(instance.getItems()[nextIndex].getId());
85
- }, updateDomFocus: () => {
66
+ (_a = tree.getItems()[nextIndex]) === null || _a === void 0 ? void 0 : _a.setFocused();
67
+ },
68
+ updateDomFocus: ({ tree }) => {
86
69
  // Required because if the state is managed outside in react, the state only updated during next render
87
70
  setTimeout(() => __awaiter(void 0, void 0, void 0, function* () {
88
71
  var _a, _b;
89
- const focusedItem = instance.getFocusedItem();
90
- (_b = (_a = instance.getConfig()).scrollToItem) === null || _b === void 0 ? void 0 : _b.call(_a, focusedItem);
72
+ const focusedItem = tree.getFocusedItem();
73
+ (_b = (_a = tree.getConfig()).scrollToItem) === null || _b === void 0 ? void 0 : _b.call(_a, focusedItem);
91
74
  yield poll(() => focusedItem.getElement() !== null, 20);
92
75
  const focusedElement = focusedItem.getElement();
93
76
  if (!focusedElement)
94
77
  return;
95
78
  focusedElement.focus();
96
79
  }));
97
- }, getContainerProps: () => {
98
- var _a;
99
- return (Object.assign(Object.assign({}, (_a = prev.getContainerProps) === null || _a === void 0 ? void 0 : _a.call(prev)), { role: "tree", ariaLabel: "", ariaActivedescendant: "" }));
100
- } })),
101
- createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { isLoading: () => {
102
- throw new Error("No data-loader registered");
103
- }, scrollTo: (scrollIntoViewArg) => __awaiter(void 0, void 0, void 0, function* () {
104
- var _a, _b;
105
- (_b = (_a = tree.getConfig()).scrollToItem) === null || _b === void 0 ? void 0 : _b.call(_a, item);
80
+ },
81
+ // TODO add label parameter
82
+ getContainerProps: ({ prev, tree }) => (Object.assign(Object.assign({}, prev === null || prev === void 0 ? void 0 : prev()), { role: "tree", "aria-label": "", ref: tree.registerElement })),
83
+ // relevant for hotkeys of this feature
84
+ isSearchOpen: () => false,
85
+ },
86
+ itemInstance: {
87
+ scrollTo: (_a, scrollIntoViewArg_1) => __awaiter(void 0, [_a, scrollIntoViewArg_1], void 0, function* ({ tree, item }, scrollIntoViewArg) {
88
+ var _b, _c, _d;
89
+ (_c = (_b = tree.getConfig()).scrollToItem) === null || _c === void 0 ? void 0 : _c.call(_b, item);
106
90
  yield poll(() => item.getElement() !== null, 20);
107
- item.getElement().scrollIntoView(scrollIntoViewArg);
108
- }), getId: () => item.getItemMeta().itemId, getProps: () => {
109
- var _a;
91
+ (_d = item.getElement()) === null || _d === void 0 ? void 0 : _d.scrollIntoView(scrollIntoViewArg);
92
+ }),
93
+ getId: ({ itemId }) => itemId,
94
+ getProps: ({ item, prev }) => {
110
95
  const itemMeta = item.getItemMeta();
111
- return Object.assign(Object.assign({}, (_a = prev.getProps) === null || _a === void 0 ? void 0 : _a.call(prev)), { 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: item.getMemoizedProp("tree/onClick", () => (e) => {
96
+ 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) => {
112
97
  item.setFocused();
113
98
  item.primaryAction();
114
99
  if (e.ctrlKey || e.shiftKey || e.metaKey) {
@@ -123,47 +108,65 @@ export const treeFeature = {
123
108
  else {
124
109
  item.expand();
125
110
  }
126
- }) });
127
- }, expand: () => tree.expandItem(item.getItemMeta().itemId), collapse: () => tree.collapseItem(item.getItemMeta().itemId), getItemData: () => tree.retrieveItemData(item.getItemMeta().itemId), isExpanded: () => tree.getState().expandedItems.includes(item.getItemMeta().itemId), isFocused: () => tree.getState().focusedItem === item.getItemMeta().itemId ||
128
- (tree.getState().focusedItem === null && item.getItemMeta().index === 0), isFolder: () => item.getItemMeta().level === -1 ||
129
- tree.getConfig().isItemFolder(item), getItemName: () => {
111
+ } });
112
+ },
113
+ expand: ({ tree, item, itemId }) => {
114
+ var _a;
115
+ if (!item.isFolder()) {
116
+ return;
117
+ }
118
+ if ((_a = tree.getState().loadingItems) === null || _a === void 0 ? void 0 : _a.includes(itemId)) {
119
+ return;
120
+ }
121
+ tree.applySubStateUpdate("expandedItems", (expandedItems) => [
122
+ ...expandedItems,
123
+ itemId,
124
+ ]);
125
+ tree.rebuildTree();
126
+ },
127
+ collapse: ({ tree, item, itemId }) => {
128
+ if (!item.isFolder()) {
129
+ return;
130
+ }
131
+ tree.applySubStateUpdate("expandedItems", (expandedItems) => expandedItems.filter((id) => id !== itemId));
132
+ tree.rebuildTree();
133
+ },
134
+ getItemData: ({ tree, itemId }) => tree.retrieveItemData(itemId),
135
+ equals: ({ item }, other) => item.getId() === (other === null || other === void 0 ? void 0 : other.getId()),
136
+ isExpanded: ({ tree, itemId }) => tree.getState().expandedItems.includes(itemId),
137
+ isDescendentOf: ({ item }, parentId) => {
138
+ const parent = item.getParent();
139
+ return Boolean((parent === null || parent === void 0 ? void 0 : parent.getId()) === parentId || (parent === null || parent === void 0 ? void 0 : parent.isDescendentOf(parentId)));
140
+ },
141
+ isFocused: ({ tree, item, itemId }) => tree.getState().focusedItem === itemId ||
142
+ (tree.getState().focusedItem === null && item.getItemMeta().index === 0),
143
+ isFolder: ({ tree, item }) => item.getItemMeta().level === -1 ||
144
+ tree.getConfig().isItemFolder(item),
145
+ getItemName: ({ tree, item }) => {
130
146
  const config = tree.getConfig();
131
147
  return config.getItemName(item);
132
- }, setFocused: () => tree.focusItem(item.getItemMeta().itemId), primaryAction: () => { var _a, _b; return (_b = (_a = tree.getConfig()).onPrimaryAction) === null || _b === void 0 ? void 0 : _b.call(_a, item); }, getParent: memo((itemMeta) => {
133
- for (let i = itemMeta.index - 1; i >= 0; i--) {
134
- const potentialParent = tree.getItems()[i];
135
- if (potentialParent.getItemMeta().level < itemMeta.level) {
136
- return potentialParent;
137
- }
138
- }
139
- return tree.getItemInstance(tree.getConfig().rootItemId);
140
- }, () => [item.getItemMeta()]),
141
- // TODO remove
142
- getIndexInParent: () => item.getItemMeta().posInSet, getChildren: () => tree
148
+ },
149
+ setFocused: ({ tree, itemId }) => {
150
+ tree.applySubStateUpdate("focusedItem", itemId);
151
+ },
152
+ primaryAction: ({ tree, item }) => { var _a, _b; return (_b = (_a = tree.getConfig()).onPrimaryAction) === null || _b === void 0 ? void 0 : _b.call(_a, item); },
153
+ getParent: ({ tree, item }) => item.getItemMeta().parentId
154
+ ? tree.getItemInstance(item.getItemMeta().parentId)
155
+ : undefined,
156
+ getIndexInParent: ({ item }) => item.getItemMeta().posInSet,
157
+ getChildren: ({ tree, item }) => tree
143
158
  .retrieveChildrenIds(item.getItemMeta().itemId)
144
- .map((id) => tree.getItemInstance(id)), getTree: () => tree, getItemAbove: () => tree.getItems()[item.getItemMeta().index - 1], getItemBelow: () => tree.getItems()[item.getItemMeta().index + 1], getMemoizedProp: (name, create, deps) => {
145
- var _a, _b, _c, _d, _e;
146
- var _f, _g;
147
- const data = item.getDataRef();
148
- const memoizedValue = (_a = data.current.memoizedValues) === null || _a === void 0 ? void 0 : _a[name];
149
- if (memoizedValue &&
150
- (!deps ||
151
- ((_c = (_b = data.current.memoizedDeps) === null || _b === void 0 ? void 0 : _b[name]) === null || _c === void 0 ? void 0 : _c.every((d, i) => d === deps[i])))) {
152
- return memoizedValue;
153
- }
154
- (_d = (_f = data.current).memoizedDeps) !== null && _d !== void 0 ? _d : (_f.memoizedDeps = {});
155
- (_e = (_g = data.current).memoizedValues) !== null && _e !== void 0 ? _e : (_g.memoizedValues = {});
156
- const value = create();
157
- data.current.memoizedDeps[name] = deps;
158
- data.current.memoizedValues[name] = value;
159
- return value;
160
- } })),
159
+ .map((id) => tree.getItemInstance(id)),
160
+ getTree: ({ tree }) => tree,
161
+ getItemAbove: ({ tree, item }) => tree.getItems()[item.getItemMeta().index - 1],
162
+ getItemBelow: ({ tree, item }) => tree.getItems()[item.getItemMeta().index + 1],
163
+ },
161
164
  hotkeys: {
162
165
  focusNextItem: {
163
166
  hotkey: "ArrowDown",
164
167
  canRepeat: true,
165
168
  preventDefault: true,
166
- isEnabled: (tree) => { var _a, _b; return !((_b = (_a = tree.isSearchOpen) === null || _a === void 0 ? void 0 : _a.call(tree)) !== null && _b !== void 0 ? _b : false) && !tree.getState().dnd; },
169
+ isEnabled: (tree) => { var _a, _b; return !((_b = (_a = tree.isSearchOpen) === null || _a === void 0 ? void 0 : _a.call(tree)) !== null && _b !== void 0 ? _b : false) && !tree.getState().dnd; }, // TODO what happens when the feature doesnt exist? proxy method still claims to exist
167
170
  handler: (e, tree) => {
168
171
  tree.focusNextItem();
169
172
  tree.updateDomFocus();
@@ -212,14 +215,16 @@ export const treeFeature = {
212
215
  focusFirstItem: {
213
216
  hotkey: "Home",
214
217
  handler: (e, tree) => {
215
- tree.focusItem(tree.getItems()[0].getId());
218
+ var _a;
219
+ (_a = tree.getItems()[0]) === null || _a === void 0 ? void 0 : _a.setFocused();
216
220
  tree.updateDomFocus();
217
221
  },
218
222
  },
219
223
  focusLastItem: {
220
224
  hotkey: "End",
221
225
  handler: (e, tree) => {
222
- tree.focusItem(tree.getItems()[tree.getItems().length - 1].getId());
226
+ var _a;
227
+ (_a = tree.getItems()[tree.getItems().length - 1]) === null || _a === void 0 ? void 0 : _a.setFocused();
223
228
  tree.updateDomFocus();
224
229
  },
225
230
  },
@@ -27,10 +27,6 @@ export type TreeFeatureDef<T> = {
27
27
  treeInstance: {
28
28
  /** @internal */
29
29
  getItemsMeta: () => ItemMeta[];
30
- expandItem: (itemId: string) => void;
31
- collapseItem: (itemId: string) => void;
32
- isItemExpanded: (itemId: string) => boolean;
33
- focusItem: (itemId: string) => void;
34
30
  getFocusedItem: () => ItemInstance<any>;
35
31
  focusNextItem: () => void;
36
32
  focusPreviousItem: () => void;
@@ -42,20 +38,21 @@ export type TreeFeatureDef<T> = {
42
38
  getProps: () => Record<string, any>;
43
39
  getItemName: () => string;
44
40
  getItemData: () => T;
41
+ equals: (other?: ItemInstance<any> | null) => boolean;
45
42
  expand: () => void;
46
43
  collapse: () => void;
47
44
  isExpanded: () => boolean;
45
+ isDescendentOf: (parentId: string) => boolean;
48
46
  isFocused: () => boolean;
49
47
  isFolder: () => boolean;
50
48
  setFocused: () => void;
51
- getParent: () => ItemInstance<T>;
49
+ getParent: () => ItemInstance<T> | undefined;
52
50
  getChildren: () => ItemInstance<T>[];
53
51
  getIndexInParent: () => number;
54
52
  primaryAction: () => void;
55
53
  getTree: () => TreeInstance<T>;
56
- getItemAbove: () => ItemInstance<T> | null;
57
- getItemBelow: () => ItemInstance<T> | null;
58
- getMemoizedProp: <X>(name: string, create: () => X, deps?: any[]) => X;
54
+ getItemAbove: () => ItemInstance<T> | undefined;
55
+ getItemBelow: () => ItemInstance<T> | undefined;
59
56
  scrollTo: (scrollIntoViewArg?: boolean | ScrollIntoViewOptions) => Promise<void>;
60
57
  };
61
58
  hotkeys: "focusNextItem" | "focusPreviousItem" | "expandOrDown" | "collapseOrUp" | "focusFirstItem" | "focusLastItem";
@@ -1,7 +1,7 @@
1
1
  export * from "./types/core";
2
2
  export * from "./core/create-tree";
3
3
  export * from "./features/tree/types";
4
- export * from "./features/main/types";
4
+ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
5
5
  export * from "./features/drag-and-drop/types";
6
6
  export * from "./features/selection/types";
7
7
  export * from "./features/async-data-loader/types";
@@ -10,6 +10,7 @@ export * from "./features/hotkeys-core/types";
10
10
  export * from "./features/search/types";
11
11
  export * from "./features/renaming/types";
12
12
  export * from "./features/expand-all/types";
13
+ export * from "./features/prop-memoization/types";
13
14
  export * from "./features/selection/feature";
14
15
  export * from "./features/hotkeys-core/feature";
15
16
  export * from "./features/async-data-loader/feature";
@@ -18,6 +19,9 @@ export * from "./features/drag-and-drop/feature";
18
19
  export * from "./features/search/feature";
19
20
  export * from "./features/renaming/feature";
20
21
  export * from "./features/expand-all/feature";
22
+ export * from "./features/prop-memoization/feature";
21
23
  export * from "./utilities/create-on-drop-handler";
22
24
  export * from "./utilities/insert-items-at-target";
23
25
  export * from "./utilities/remove-items-from-parents";
26
+ export * from "./core/build-proxified-instance";
27
+ export * from "./core/build-static-instance";
package/lib/esm/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from "./types/core";
2
2
  export * from "./core/create-tree";
3
3
  export * from "./features/tree/types";
4
- export * from "./features/main/types";
5
4
  export * from "./features/drag-and-drop/types";
6
5
  export * from "./features/selection/types";
7
6
  export * from "./features/async-data-loader/types";
@@ -10,6 +9,7 @@ export * from "./features/hotkeys-core/types";
10
9
  export * from "./features/search/types";
11
10
  export * from "./features/renaming/types";
12
11
  export * from "./features/expand-all/types";
12
+ export * from "./features/prop-memoization/types";
13
13
  export * from "./features/selection/feature";
14
14
  export * from "./features/hotkeys-core/feature";
15
15
  export * from "./features/async-data-loader/feature";
@@ -18,6 +18,9 @@ export * from "./features/drag-and-drop/feature";
18
18
  export * from "./features/search/feature";
19
19
  export * from "./features/renaming/feature";
20
20
  export * from "./features/expand-all/feature";
21
+ export * from "./features/prop-memoization/feature";
21
22
  export * from "./utilities/create-on-drop-handler";
22
23
  export * from "./utilities/insert-items-at-target";
23
24
  export * from "./utilities/remove-items-from-parents";
25
+ export * from "./core/build-proxified-instance";
26
+ export * from "./core/build-static-instance";
@@ -8,6 +8,7 @@ import { SearchFeatureDef } from "./features/search/types";
8
8
  import { SelectionFeatureDef } from "./features/selection/types";
9
9
  import { SyncDataLoaderFeatureDef } from "./features/sync-data-loader/types";
10
10
  import { TreeFeatureDef } from "./features/tree/types";
11
+ import { PropMemoizationFeatureDef } from "./features/prop-memoization/types";
11
12
  export * from ".";
12
13
  /** @interface */
13
14
  export type AsyncDataLoaderFeatureConfig<T> = AsyncDataLoaderFeatureDef<T>["config"];
@@ -55,6 +56,15 @@ export type MainFeatureTreeInstance = MainFeatureDef["treeInstance"];
55
56
  export type MainFeatureItemInstance = MainFeatureDef["itemInstance"];
56
57
  export type MainFeatureHotkeys = MainFeatureDef["hotkeys"];
57
58
  /** @interface */
59
+ export type PropMemoizationConfig = PropMemoizationFeatureDef["config"];
60
+ /** @interface */
61
+ export type PropMemoizationState = PropMemoizationFeatureDef["state"];
62
+ /** @interface */
63
+ export type PropMemoizationTreeInstance = PropMemoizationFeatureDef["treeInstance"];
64
+ /** @interface */
65
+ export type PropMemoizationItemInstance = PropMemoizationFeatureDef["itemInstance"];
66
+ export type PropMemoizationHotkeys = PropMemoizationFeatureDef["hotkeys"];
67
+ /** @interface */
58
68
  export type RenamingFeatureConfig<T> = RenamingFeatureDef<T>["config"];
59
69
  /** @interface */
60
70
  export type RenamingFeatureState<T> = RenamingFeatureDef<T>["state"];
@@ -0,0 +1,23 @@
1
+ import { DragEvent } from "react";
2
+ import { TestTree } from "./test-tree";
3
+ import { HotkeyName } from "../types/core";
4
+ export declare class TestTreeDo<T> {
5
+ protected tree: TestTree<T>;
6
+ protected itemInstance(itemId: string): import("../types/core").ItemInstance<T>;
7
+ protected itemProps(itemId: string): Record<string, any>;
8
+ constructor(tree: TestTree<T>);
9
+ selectItem(id: string): void;
10
+ shiftSelectItem(id: string): void;
11
+ ctrlSelectItem(id: string): void;
12
+ ctrlShiftSelectItem(id: string): void;
13
+ selectMultiple(...ids: string[]): void;
14
+ hotkey(hotkey: HotkeyName, e?: Partial<KeyboardEvent>): void;
15
+ startDrag(itemId: string, event?: DragEvent): DragEvent<Element>;
16
+ dragOver(itemId: string, event?: DragEvent): DragEvent<Element>;
17
+ dragOverNotAllowed(itemId: string, event?: DragEvent): DragEvent<Element>;
18
+ dragLeave(itemId: string): void;
19
+ dragEnd(itemId: string, event?: DragEvent): DragEvent<Element>;
20
+ drop(itemId: string, event?: DragEvent): DragEvent<Element>;
21
+ dragOverAndDrop(itemId: string, event?: DragEvent): DragEvent<Element>;
22
+ private consistentCalls;
23
+ }