@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
@@ -0,0 +1,475 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { TestTree } from "../../test-utils/test-tree";
3
+ import { propMemoizationFeature } from "../prop-memoization/feature";
4
+
5
+ const factory = TestTree.default({}).withFeatures(propMemoizationFeature);
6
+
7
+ describe("core-feature/selections", () => {
8
+ factory.forSuits((tree) => {
9
+ describe("expanded items", () => {
10
+ it("can expand via tree instance", () => {
11
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
12
+ tree.item("x2").expand();
13
+ expect(setExpandedItems).toHaveBeenCalledWith(["x1", "x11", "x2"]);
14
+ });
15
+
16
+ it("can expand via item", () => {
17
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
18
+ tree.instance.getItemInstance("x2").expand();
19
+ expect(setExpandedItems).toHaveBeenCalledWith(["x1", "x11", "x2"]);
20
+ });
21
+
22
+ it("returns correct isItemExpanded value", () => {
23
+ expect(tree.item("x2").isExpanded()).toBe(false);
24
+ tree.item("x2").expand();
25
+ expect(tree.item("x2").isExpanded()).toBe(true);
26
+ });
27
+
28
+ it("calls setState", () => {
29
+ const setState = tree.mockedHandler("setState");
30
+ tree.item("x2").expand();
31
+ expect(setState).toBeCalledWith(
32
+ expect.objectContaining({ expandedItems: ["x1", "x11", "x2"] }),
33
+ );
34
+ });
35
+
36
+ it("expands on click", () => {
37
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
38
+ tree.do.selectItem("x2");
39
+ expect(setExpandedItems).toHaveBeenCalledWith(["x1", "x11", "x2"]);
40
+ });
41
+ });
42
+
43
+ describe("collapsed items", () => {
44
+ it("can collapse via tree instance", () => {
45
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
46
+ tree.item("x1").collapse();
47
+ expect(setExpandedItems).toHaveBeenCalledWith(["x11"]);
48
+ });
49
+
50
+ it("can collapse via item", () => {
51
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
52
+ tree.instance.getItemInstance("x1").collapse();
53
+ expect(setExpandedItems).toHaveBeenCalledWith(["x11"]);
54
+ });
55
+
56
+ it("returns correct isItemExpanded value", () => {
57
+ expect(tree.item("x1").isExpanded()).toBe(true);
58
+ tree.item("x1").collapse();
59
+ expect(tree.item("x1").isExpanded()).toBe(false);
60
+ });
61
+
62
+ it("calls setState", () => {
63
+ const setState = tree.mockedHandler("setState");
64
+ tree.item("x1").collapse();
65
+ expect(setState).toBeCalledWith(
66
+ expect.objectContaining({ expandedItems: ["x11"] }),
67
+ );
68
+ });
69
+
70
+ it("collapses on click", () => {
71
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
72
+ tree.do.selectItem("x1");
73
+ expect(setExpandedItems).toHaveBeenCalledWith(["x11"]);
74
+ });
75
+ });
76
+
77
+ describe("focused item", () => {
78
+ it("focuses item", () => {
79
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
80
+ tree.item("x11").setFocused();
81
+ expect(setFocusedItem).toHaveBeenCalledWith("x11");
82
+ });
83
+
84
+ it("returns correct initial focused item", () => {
85
+ expect(tree.instance.getFocusedItem().getId()).toBe("x1");
86
+ });
87
+
88
+ it("returns correct focused item", () => {
89
+ tree.item("x11").setFocused();
90
+ expect(tree.instance.getFocusedItem().getId()).toBe("x11");
91
+ });
92
+
93
+ it("calls setState", () => {
94
+ const setState = tree.mockedHandler("setState");
95
+ tree.item("x1").setFocused();
96
+ expect(setState).toBeCalledWith(
97
+ expect.objectContaining({ focusedItem: "x1" }),
98
+ );
99
+ });
100
+
101
+ it("updates dom focus", async () => {
102
+ const scrollToItem = tree.mockedHandler("scrollToItem");
103
+ const element = { focus: vi.fn() };
104
+ tree.instance.getItemInstance("x2").registerElement(element as any);
105
+ tree.item("x2").setFocused();
106
+ tree.instance.updateDomFocus();
107
+ vi.runAllTimers();
108
+ await vi.waitFor(() =>
109
+ expect(scrollToItem).toBeCalledWith(tree.instance.getFocusedItem()),
110
+ );
111
+ expect(element.focus).toBeCalled();
112
+ });
113
+
114
+ describe("move focus", () => {
115
+ it("focuses next item", () => {
116
+ tree.item("x2").setFocused();
117
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
118
+ tree.instance.focusNextItem();
119
+ expect(setFocusedItem).toHaveBeenCalledWith("x3");
120
+ });
121
+
122
+ it("focuses previous item", () => {
123
+ tree.item("x2").setFocused();
124
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
125
+ tree.instance.focusPreviousItem();
126
+ expect(setFocusedItem).toHaveBeenCalledWith("x14");
127
+ });
128
+
129
+ it("remains at last item when focus is at bottom", () => {
130
+ tree.item("x4").setFocused();
131
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
132
+ tree.instance.focusNextItem();
133
+ expect(setFocusedItem).toHaveBeenCalledWith("x4");
134
+ expect(setFocusedItem).toHaveBeenCalledTimes(1);
135
+ });
136
+
137
+ it("remains at first item when focus is at top", () => {
138
+ tree.item("x1").setFocused();
139
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
140
+ tree.instance.focusPreviousItem();
141
+ expect(setFocusedItem).toHaveBeenCalledWith("x1");
142
+ expect(setFocusedItem).toHaveBeenCalledTimes(1);
143
+ });
144
+ });
145
+ });
146
+
147
+ describe("items meta", () => {
148
+ it("root item", () => {
149
+ expect(tree.instance.getItemInstance("x").getItemMeta()).toEqual({
150
+ index: -1,
151
+ itemId: "x",
152
+ level: -1,
153
+ parentId: null,
154
+ posInSet: 0,
155
+ setSize: 1,
156
+ });
157
+ });
158
+
159
+ it("expanded container", () => {
160
+ expect(tree.instance.getItemInstance("x11").getItemMeta()).toEqual({
161
+ index: 1,
162
+ itemId: "x11",
163
+ level: 1,
164
+ parentId: "x1",
165
+ posInSet: 0,
166
+ setSize: 4,
167
+ });
168
+ });
169
+
170
+ it("top leaf", () => {
171
+ expect(tree.instance.getItemInstance("x111").getItemMeta()).toEqual({
172
+ index: 2,
173
+ itemId: "x111",
174
+ level: 2,
175
+ parentId: "x11",
176
+ posInSet: 0,
177
+ setSize: 4,
178
+ });
179
+ });
180
+
181
+ it("middle leaf", () => {
182
+ expect(tree.instance.getItemInstance("x112").getItemMeta()).toEqual({
183
+ index: 3,
184
+ itemId: "x112",
185
+ level: 2,
186
+ parentId: "x11",
187
+ posInSet: 1,
188
+ setSize: 4,
189
+ });
190
+ });
191
+
192
+ it("bottom leaf", () => {
193
+ expect(tree.instance.getItemInstance("x114").getItemMeta()).toEqual({
194
+ index: 5,
195
+ itemId: "x114",
196
+ level: 2,
197
+ parentId: "x11",
198
+ posInSet: 3,
199
+ setSize: 4,
200
+ });
201
+ });
202
+
203
+ it("item after expanded contents", () => {
204
+ expect(tree.instance.getItemInstance("x2").getItemMeta()).toEqual({
205
+ index: 9,
206
+ itemId: "x2",
207
+ level: 0,
208
+ parentId: "x",
209
+ posInSet: 1,
210
+ setSize: 4,
211
+ });
212
+ });
213
+
214
+ it("last item", () => {
215
+ expect(tree.instance.getItemInstance("x2").getItemMeta()).toEqual({
216
+ index: 9,
217
+ itemId: "x2",
218
+ level: 0,
219
+ parentId: "x",
220
+ posInSet: 1,
221
+ setSize: 4,
222
+ });
223
+ });
224
+ });
225
+
226
+ describe("props generation", () => {
227
+ it("generates container props", () => {
228
+ expect(tree.instance.getContainerProps()).toEqual(
229
+ expect.objectContaining({
230
+ role: "tree",
231
+ }),
232
+ );
233
+ });
234
+
235
+ it("generates item props for random item", () => {
236
+ expect(tree.instance.getItemInstance("x2").getProps()).toEqual({
237
+ "aria-label": "x2",
238
+ "aria-level": 0,
239
+ "aria-posinset": 1,
240
+ "aria-selected": "false",
241
+ "aria-setsize": 4,
242
+ onClick: expect.any(Function),
243
+ ref: expect.any(Function),
244
+ role: "treeitem",
245
+ tabIndex: -1,
246
+ });
247
+ });
248
+
249
+ it("generates item props for focused", () => {
250
+ expect(tree.instance.getItemInstance("x1").getProps()).toEqual({
251
+ "aria-label": "x1",
252
+ "aria-level": 0,
253
+ "aria-posinset": 0,
254
+ "aria-selected": "false",
255
+ "aria-setsize": 4,
256
+ onClick: expect.any(Function),
257
+ ref: expect.any(Function),
258
+ role: "treeitem",
259
+ tabIndex: 0,
260
+ });
261
+ });
262
+ });
263
+
264
+ describe("util functions", () => {
265
+ it("returns correctly for getId()", () => {
266
+ expect(tree.instance.getItemInstance("x2").getId()).toBe("x2");
267
+ });
268
+
269
+ it("returns correctly for getItemName()", () => {
270
+ expect(tree.instance.getItemInstance("x2").getItemName()).toBe("x2");
271
+ });
272
+
273
+ it("returns correctly for getItemData()", () => {
274
+ expect(tree.instance.getItemInstance("x2").getItemData()).toBe("x2");
275
+ });
276
+
277
+ it("returns correctly for true cases of isExpanded()", () => {
278
+ expect(tree.instance.getItemInstance("x1").isExpanded()).toBe(true);
279
+ });
280
+
281
+ it("returns correctly for false cases of isExpanded()", () => {
282
+ expect(tree.instance.getItemInstance("x12").isExpanded()).toBe(false);
283
+ });
284
+
285
+ it("returns correctly for direct descendants of isDescendentOf()", () => {
286
+ expect(
287
+ tree.instance.getItemInstance("x111").isDescendentOf("x11"),
288
+ ).toBe(true);
289
+ });
290
+
291
+ it("returns correctly for indirect descendants of isDescendentOf()", () => {
292
+ expect(tree.instance.getItemInstance("x111").isDescendentOf("x")).toBe(
293
+ true,
294
+ );
295
+ });
296
+
297
+ it("returns correctly for non-descendants of isDescendentOf()", () => {
298
+ expect(tree.instance.getItemInstance("x12").isDescendentOf("x2")).toBe(
299
+ false,
300
+ );
301
+ });
302
+
303
+ it("returns correctly for true cases of isFocused()", () => {
304
+ expect(tree.instance.getItemInstance("x1").isFocused()).toBe(true);
305
+ });
306
+
307
+ it("returns correctly for false cases of isFocused()", () => {
308
+ expect(tree.instance.getItemInstance("x2").isFocused()).toBe(false);
309
+ });
310
+
311
+ it("returns correctly for true cases of isFolder()", () => {
312
+ expect(tree.instance.getItemInstance("x1").isFolder()).toBe(true);
313
+ });
314
+
315
+ it("returns correctly for false cases of isFolder()", () => {
316
+ expect(tree.instance.getItemInstance("x111").isFolder()).toBe(false);
317
+ });
318
+
319
+ it("returns correctly for getParent()", () => {
320
+ expect(tree.instance.getItemInstance("x111").getParent()?.getId()).toBe(
321
+ "x11",
322
+ );
323
+ });
324
+
325
+ it("returns correctly for getChildren()", () => {
326
+ expect(
327
+ tree.instance
328
+ .getItemInstance("x1")
329
+ .getChildren()
330
+ .map((i) => i.getId()),
331
+ ).toEqual(["x11", "x12", "x13", "x14"]);
332
+ });
333
+
334
+ it("returns correctly for getIndexInParent()", () => {
335
+ expect(tree.instance.getItemInstance("x113").getIndexInParent()).toBe(
336
+ 2,
337
+ );
338
+ });
339
+
340
+ it("returns correctly for getTree()", () => {
341
+ expect(tree.instance.getItemInstance("x111").getTree()).toBe(
342
+ tree.instance,
343
+ );
344
+ });
345
+ });
346
+
347
+ describe("primary action", () => {
348
+ it("calls primary action", () => {
349
+ const onPrimaryAction = tree.mockedHandler("onPrimaryAction");
350
+ tree.instance.getItemInstance("x2").primaryAction();
351
+ expect(onPrimaryAction).toHaveBeenCalledWith(
352
+ tree.instance.getItemInstance("x2"),
353
+ );
354
+ });
355
+ });
356
+
357
+ describe("item above/below", () => {
358
+ it("returns correctly for getItemAbove()", () => {
359
+ expect(
360
+ tree.instance.getItemInstance("x12").getItemAbove()?.getId(),
361
+ ).toBe("x114");
362
+ });
363
+
364
+ it("returns correctly for getItemAbove() at top", () => {
365
+ expect(tree.instance.getItemInstance("x1").getItemAbove()).toBe(
366
+ undefined,
367
+ );
368
+ });
369
+
370
+ it("returns correctly for getItemBelow()", () => {
371
+ expect(
372
+ tree.instance.getItemInstance("x14").getItemBelow()?.getId(),
373
+ ).toBe("x2");
374
+ });
375
+
376
+ it("returns correctly for getItemBelow() at bottom", () => {
377
+ expect(tree.instance.getItemInstance("x4").getItemBelow()).toBe(
378
+ undefined,
379
+ );
380
+ });
381
+ });
382
+
383
+ describe("hotkeys", () => {
384
+ it("focuses next item", () => {
385
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
386
+ tree.do.selectItem("x112");
387
+ tree.do.hotkey("focusNextItem");
388
+ expect(setFocusedItem).toHaveBeenCalledWith("x113");
389
+ });
390
+
391
+ it("focuses previous item", () => {
392
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
393
+ tree.do.selectItem("x112");
394
+ tree.do.hotkey("focusPreviousItem");
395
+ expect(setFocusedItem).toHaveBeenCalledWith("x111");
396
+ });
397
+
398
+ it("runs expandOrDown for expandable folder", () => {
399
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
400
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
401
+ tree.do.ctrlSelectItem("x2");
402
+ setFocusedItem.mockClear();
403
+ tree.do.hotkey("expandOrDown");
404
+ expect(setExpandedItems).toHaveBeenCalledWith(["x1", "x11", "x2"]);
405
+ expect(setFocusedItem).not.toBeCalled();
406
+ });
407
+
408
+ it("runs expandOrDown for expanded folder", () => {
409
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
410
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
411
+ tree.do.ctrlSelectItem("x1");
412
+ setFocusedItem.mockClear();
413
+ tree.do.hotkey("expandOrDown");
414
+ expect(setExpandedItems).not.toBeCalled();
415
+ expect(setFocusedItem).toHaveBeenCalledWith("x11");
416
+ });
417
+
418
+ it("runs expandOrDown for non-folder item", () => {
419
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
420
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
421
+ tree.do.selectItem("x111");
422
+ setFocusedItem.mockClear();
423
+ tree.do.hotkey("expandOrDown");
424
+ expect(setExpandedItems).not.toBeCalled();
425
+ expect(setFocusedItem).toHaveBeenCalledWith("x112");
426
+ });
427
+
428
+ it("runs collapseOrUp for expanded folder", () => {
429
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
430
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
431
+ tree.do.ctrlSelectItem("x1");
432
+ setFocusedItem.mockClear();
433
+ tree.do.hotkey("collapseOrUp");
434
+ expect(setExpandedItems).toHaveBeenCalledWith(["x11"]);
435
+ expect(setFocusedItem).not.toBeCalled();
436
+ });
437
+
438
+ it("runs collapseOrUp for collapsed folder", () => {
439
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
440
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
441
+ tree.do.ctrlSelectItem("x2");
442
+ setFocusedItem.mockClear();
443
+ tree.do.hotkey("collapseOrUp");
444
+ expect(setExpandedItems).toBeCalledWith(["x1", "x11"]);
445
+ expect(setFocusedItem).not.toBeCalled();
446
+ });
447
+
448
+ it("runs collapseOrUp for non-folder item", () => {
449
+ const setExpandedItems = tree.mockedHandler("setExpandedItems");
450
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
451
+ tree.do.ctrlSelectItem("x112");
452
+ setFocusedItem.mockClear();
453
+ tree.do.hotkey("collapseOrUp");
454
+ expect(setExpandedItems).not.toBeCalled();
455
+ expect(setFocusedItem).toHaveBeenCalledWith("x11");
456
+ });
457
+
458
+ it("runs focusFirstItem", () => {
459
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
460
+ tree.do.selectItem("x2");
461
+ setFocusedItem.mockClear();
462
+ tree.do.hotkey("focusFirstItem");
463
+ expect(setFocusedItem).toHaveBeenCalledWith("x1");
464
+ });
465
+
466
+ it("runs focusLastItem", () => {
467
+ const setFocusedItem = tree.mockedHandler("setFocusedItem");
468
+ tree.do.selectItem("x2");
469
+ setFocusedItem.mockClear();
470
+ tree.do.hotkey("focusLastItem");
471
+ expect(setFocusedItem).toHaveBeenCalledWith("x4");
472
+ });
473
+ });
474
+ });
475
+ });
@@ -33,11 +33,6 @@ export type TreeFeatureDef<T> = {
33
33
  /** @internal */
34
34
  getItemsMeta: () => ItemMeta[];
35
35
 
36
- expandItem: (itemId: string) => void;
37
- collapseItem: (itemId: string) => void;
38
- isItemExpanded: (itemId: string) => boolean;
39
-
40
- focusItem: (itemId: string) => void;
41
36
  getFocusedItem: () => ItemInstance<any>;
42
37
  focusNextItem: () => void;
43
38
  focusPreviousItem: () => void;
@@ -50,20 +45,21 @@ export type TreeFeatureDef<T> = {
50
45
  getProps: () => Record<string, any>;
51
46
  getItemName: () => string;
52
47
  getItemData: () => T;
48
+ equals: (other?: ItemInstance<any> | null) => boolean;
53
49
  expand: () => void;
54
50
  collapse: () => void;
55
51
  isExpanded: () => boolean;
52
+ isDescendentOf: (parentId: string) => boolean;
56
53
  isFocused: () => boolean;
57
54
  isFolder: () => boolean;
58
55
  setFocused: () => void;
59
- getParent: () => ItemInstance<T>;
56
+ getParent: () => ItemInstance<T> | undefined;
60
57
  getChildren: () => ItemInstance<T>[];
61
58
  getIndexInParent: () => number;
62
59
  primaryAction: () => void;
63
60
  getTree: () => TreeInstance<T>;
64
- getItemAbove: () => ItemInstance<T> | null;
65
- getItemBelow: () => ItemInstance<T> | null;
66
- getMemoizedProp: <X>(name: string, create: () => X, deps?: any[]) => X;
61
+ getItemAbove: () => ItemInstance<T> | undefined;
62
+ getItemBelow: () => ItemInstance<T> | undefined;
67
63
  scrollTo: (
68
64
  scrollIntoViewArg?: boolean | ScrollIntoViewOptions,
69
65
  ) => Promise<void>;
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ export * from "./types/core";
2
2
  export * from "./core/create-tree";
3
3
 
4
4
  export * from "./features/tree/types";
5
- export * from "./features/main/types";
5
+ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
6
6
  export * from "./features/drag-and-drop/types";
7
7
  export * from "./features/selection/types";
8
8
  export * from "./features/async-data-loader/types";
@@ -11,6 +11,7 @@ export * from "./features/hotkeys-core/types";
11
11
  export * from "./features/search/types";
12
12
  export * from "./features/renaming/types";
13
13
  export * from "./features/expand-all/types";
14
+ export * from "./features/prop-memoization/types";
14
15
 
15
16
  export * from "./features/selection/feature";
16
17
  export * from "./features/hotkeys-core/feature";
@@ -20,7 +21,11 @@ export * from "./features/drag-and-drop/feature";
20
21
  export * from "./features/search/feature";
21
22
  export * from "./features/renaming/feature";
22
23
  export * from "./features/expand-all/feature";
24
+ export * from "./features/prop-memoization/feature";
23
25
 
24
26
  export * from "./utilities/create-on-drop-handler";
25
27
  export * from "./utilities/insert-items-at-target";
26
28
  export * from "./utilities/remove-items-from-parents";
29
+
30
+ export * from "./core/build-proxified-instance";
31
+ 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
 
12
13
  export * from ".";
13
14
 
@@ -80,6 +81,18 @@ export type MainFeatureTreeInstance = MainFeatureDef["treeInstance"];
80
81
  export type MainFeatureItemInstance = MainFeatureDef["itemInstance"];
81
82
  export type MainFeatureHotkeys = MainFeatureDef["hotkeys"];
82
83
 
84
+ /** @interface */
85
+ export type PropMemoizationConfig = PropMemoizationFeatureDef["config"];
86
+ /** @interface */
87
+ export type PropMemoizationState = PropMemoizationFeatureDef["state"];
88
+ /** @interface */
89
+ export type PropMemoizationTreeInstance =
90
+ PropMemoizationFeatureDef["treeInstance"];
91
+ /** @interface */
92
+ export type PropMemoizationItemInstance =
93
+ PropMemoizationFeatureDef["itemInstance"];
94
+ export type PropMemoizationHotkeys = PropMemoizationFeatureDef["hotkeys"];
95
+
83
96
  /** @interface */
84
97
  export type RenamingFeatureConfig<T> = RenamingFeatureDef<T>["config"];
85
98
  /** @interface */