@headless-tree/core 0.0.4 → 0.0.6

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 (91) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/cjs/core/create-tree.js +11 -10
  3. package/lib/cjs/features/async-data-loader/feature.js +30 -21
  4. package/lib/cjs/features/drag-and-drop/feature.js +34 -22
  5. package/lib/cjs/features/drag-and-drop/types.d.ts +14 -1
  6. package/lib/cjs/features/drag-and-drop/utils.d.ts +1 -1
  7. package/lib/cjs/features/drag-and-drop/utils.js +34 -17
  8. package/lib/cjs/features/expand-all/feature.js +12 -9
  9. package/lib/cjs/features/expand-all/types.d.ts +2 -2
  10. package/lib/cjs/features/main/types.d.ts +4 -1
  11. package/lib/cjs/features/renaming/feature.js +10 -9
  12. package/lib/cjs/features/search/feature.js +21 -7
  13. package/lib/cjs/features/search/types.d.ts +1 -1
  14. package/lib/cjs/features/selection/feature.js +6 -4
  15. package/lib/cjs/features/sync-data-loader/feature.js +6 -0
  16. package/lib/cjs/features/sync-data-loader/types.d.ts +1 -1
  17. package/lib/cjs/features/tree/feature.js +41 -24
  18. package/lib/cjs/features/tree/types.d.ts +7 -2
  19. package/lib/cjs/index.d.ts +3 -1
  20. package/lib/cjs/index.js +3 -1
  21. package/lib/cjs/types/core.d.ts +1 -3
  22. package/lib/cjs/utilities/create-on-drop-handler.d.ts +3 -0
  23. package/lib/cjs/utilities/create-on-drop-handler.js +11 -0
  24. package/lib/cjs/utilities/insert-items-at-target.d.ts +3 -0
  25. package/lib/cjs/utilities/insert-items-at-target.js +24 -0
  26. package/lib/cjs/utilities/remove-items-from-parents.d.ts +2 -0
  27. package/lib/cjs/utilities/remove-items-from-parents.js +17 -0
  28. package/lib/cjs/utils.d.ts +1 -4
  29. package/lib/cjs/utils.js +1 -53
  30. package/lib/esm/core/create-tree.js +11 -10
  31. package/lib/esm/features/async-data-loader/feature.js +30 -21
  32. package/lib/esm/features/drag-and-drop/feature.js +34 -22
  33. package/lib/esm/features/drag-and-drop/types.d.ts +14 -1
  34. package/lib/esm/features/drag-and-drop/utils.d.ts +1 -1
  35. package/lib/esm/features/drag-and-drop/utils.js +35 -18
  36. package/lib/esm/features/expand-all/feature.js +12 -9
  37. package/lib/esm/features/expand-all/types.d.ts +2 -2
  38. package/lib/esm/features/main/types.d.ts +4 -1
  39. package/lib/esm/features/renaming/feature.js +10 -9
  40. package/lib/esm/features/search/feature.js +21 -7
  41. package/lib/esm/features/search/types.d.ts +1 -1
  42. package/lib/esm/features/selection/feature.js +6 -4
  43. package/lib/esm/features/sync-data-loader/feature.js +6 -0
  44. package/lib/esm/features/sync-data-loader/types.d.ts +1 -1
  45. package/lib/esm/features/tree/feature.js +41 -24
  46. package/lib/esm/features/tree/types.d.ts +7 -2
  47. package/lib/esm/index.d.ts +3 -1
  48. package/lib/esm/index.js +3 -1
  49. package/lib/esm/types/core.d.ts +1 -3
  50. package/lib/esm/utilities/create-on-drop-handler.d.ts +3 -0
  51. package/lib/esm/utilities/create-on-drop-handler.js +7 -0
  52. package/lib/esm/utilities/insert-items-at-target.d.ts +3 -0
  53. package/lib/esm/utilities/insert-items-at-target.js +20 -0
  54. package/lib/esm/utilities/remove-items-from-parents.d.ts +2 -0
  55. package/lib/esm/utilities/remove-items-from-parents.js +13 -0
  56. package/lib/esm/utils.d.ts +1 -4
  57. package/lib/esm/utils.js +0 -50
  58. package/package.json +3 -3
  59. package/src/core/create-tree.ts +12 -8
  60. package/src/features/async-data-loader/feature.ts +15 -5
  61. package/src/features/drag-and-drop/feature.ts +42 -20
  62. package/src/features/drag-and-drop/types.ts +23 -6
  63. package/src/features/drag-and-drop/utils.ts +53 -24
  64. package/src/features/expand-all/feature.ts +10 -8
  65. package/src/features/expand-all/types.ts +2 -2
  66. package/src/features/main/types.ts +7 -0
  67. package/src/features/renaming/feature.ts +10 -5
  68. package/src/features/search/feature.ts +22 -5
  69. package/src/features/search/types.ts +1 -0
  70. package/src/features/selection/feature.ts +8 -3
  71. package/src/features/sync-data-loader/feature.ts +17 -2
  72. package/src/features/sync-data-loader/types.ts +1 -1
  73. package/src/features/tree/feature.ts +43 -23
  74. package/src/features/tree/types.ts +10 -2
  75. package/src/index.ts +4 -1
  76. package/src/types/core.ts +4 -4
  77. package/src/utilities/create-on-drop-handler.ts +14 -0
  78. package/src/utilities/insert-items-at-target.ts +30 -0
  79. package/src/utilities/remove-items-from-parents.ts +21 -0
  80. package/src/utils.ts +1 -69
  81. package/tsconfig.json +1 -1
  82. package/lib/cjs/data-adapters/nested-data-adapter.d.ts +0 -9
  83. package/lib/cjs/data-adapters/nested-data-adapter.js +0 -32
  84. package/lib/cjs/data-adapters/types.d.ts +0 -7
  85. package/lib/cjs/data-adapters/types.js +0 -2
  86. package/lib/esm/data-adapters/nested-data-adapter.d.ts +0 -9
  87. package/lib/esm/data-adapters/nested-data-adapter.js +0 -28
  88. package/lib/esm/data-adapters/types.d.ts +0 -7
  89. package/lib/esm/data-adapters/types.js +0 -1
  90. package/src/data-adapters/nested-data-adapter.ts +0 -48
  91. package/src/data-adapters/types.ts +0 -9
@@ -3,15 +3,18 @@ import { makeStateUpdater } from "../../utils";
3
3
  export const dragAndDropFeature = {
4
4
  key: "dragAndDrop",
5
5
  dependingFeatures: ["main", "tree", "selection"],
6
- getDefaultConfig: (defaultConfig, tree) => (Object.assign({ canDrop: (_, target) => target.item.isFolder(), setDndState: makeStateUpdater("dnd", tree) }, defaultConfig)),
6
+ getDefaultConfig: (defaultConfig, tree) => (Object.assign({ canDrop: (_, target) => target.item.isFolder(), canDropForeignDragObject: () => false, setDndState: makeStateUpdater("dnd", tree) }, defaultConfig)),
7
+ stateHandlerNames: {
8
+ dnd: "setDndState",
9
+ },
7
10
  createTreeInstance: (prev, tree) => (Object.assign(Object.assign({}, prev), { getDropTarget: () => {
8
11
  var _a, _b;
9
12
  return (_b = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.dragTarget) !== null && _b !== void 0 ? _b : null;
10
13
  } })),
11
14
  createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { getProps: () => {
12
15
  var _a, _b, _c;
13
- return (Object.assign(Object.assign({}, prev.getProps()), { draggable: (_c = (_b = (_a = tree.getConfig()).isItemDraggable) === null || _b === void 0 ? void 0 : _b.call(_a, item)) !== null && _c !== void 0 ? _c : true, onDragStart: (e) => {
14
- var _a, _b, _c, _d, _e;
16
+ return (Object.assign(Object.assign({}, prev.getProps()), { draggable: (_c = (_b = (_a = tree.getConfig()).isItemDraggable) === null || _b === void 0 ? void 0 : _b.call(_a, item)) !== null && _c !== void 0 ? _c : true, onDragStart: item.getMemoizedProp("dnd/onDragStart", () => (e) => {
17
+ var _a, _b, _c;
15
18
  const selectedItems = tree.getSelectedItems();
16
19
  const items = selectedItems.includes(item) ? selectedItems : [item];
17
20
  const config = tree.getConfig();
@@ -26,12 +29,12 @@ export const dragAndDropFeature = {
26
29
  const { format, data } = config.createForeignDragObject(items);
27
30
  (_c = e.dataTransfer) === null || _c === void 0 ? void 0 : _c.setData(format, data);
28
31
  }
29
- (_e = (_d = tree.getConfig()).setDndState) === null || _e === void 0 ? void 0 : _e.call(_d, {
32
+ tree.applySubStateUpdate("dnd", {
30
33
  draggedItems: items,
31
34
  draggingOverItem: tree.getFocusedItem(),
32
35
  });
33
- }, onDragOver: (e) => {
34
- var _a, _b, _c, _d, _e;
36
+ }), onDragOver: item.getMemoizedProp("dnd/onDragOver", () => (e) => {
37
+ var _a, _b, _c;
35
38
  const target = getDropTarget(e, item, tree);
36
39
  const dataRef = tree.getDataRef();
37
40
  if (!((_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems) &&
@@ -47,14 +50,21 @@ export const dragAndDropFeature = {
47
50
  return;
48
51
  }
49
52
  dataRef.current.lastDragCode = nextDragCode;
50
- (_e = (_d = tree.getConfig()).setDndState) === null || _e === void 0 ? void 0 : _e.call(_d, (state) => (Object.assign(Object.assign({}, state), { dragTarget: target, draggingOverItem: item })));
51
- }, onDragLeave: () => {
52
- var _a, _b;
53
+ tree.applySubStateUpdate("dnd", (state) => (Object.assign(Object.assign({}, state), { dragTarget: target, draggingOverItem: item })));
54
+ }), onDragLeave: item.getMemoizedProp("dnd/onDragLeave", () => () => {
53
55
  const dataRef = tree.getDataRef();
54
56
  dataRef.current.lastDragCode = "no-drag";
55
- (_b = (_a = tree.getConfig()).setDndState) === null || _b === void 0 ? void 0 : _b.call(_a, (state) => (Object.assign(Object.assign({}, state), { draggingOverItem: undefined, dragTarget: undefined })));
56
- }, onDrop: (e) => {
57
- var _a, _b, _c, _d, _e;
57
+ tree.applySubStateUpdate("dnd", (state) => (Object.assign(Object.assign({}, state), { draggingOverItem: undefined, dragTarget: undefined })));
58
+ }), onDragEnd: item.getMemoizedProp("dnd/onDragEnd", () => (e) => {
59
+ var _a, _b, _c;
60
+ const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
61
+ tree.applySubStateUpdate("dnd", null);
62
+ if (e.dataTransfer.dropEffect === "none" || !draggedItems) {
63
+ return;
64
+ }
65
+ (_c = (_b = tree.getConfig()).onCompleteForeignDrop) === null || _c === void 0 ? void 0 : _c.call(_b, draggedItems);
66
+ }), onDrop: item.getMemoizedProp("dnd/onDrop", () => (e) => {
67
+ var _a, _b, _c;
58
68
  const dataRef = tree.getDataRef();
59
69
  const target = getDropTarget(e, item, tree);
60
70
  if (!canDrop(e.dataTransfer, target, tree)) {
@@ -64,30 +74,32 @@ export const dragAndDropFeature = {
64
74
  const config = tree.getConfig();
65
75
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
66
76
  dataRef.current.lastDragCode = undefined;
67
- (_c = (_b = tree.getConfig()).setDndState) === null || _c === void 0 ? void 0 : _c.call(_b, null);
77
+ tree.applySubStateUpdate("dnd", null);
68
78
  if (draggedItems) {
69
- (_d = config.onDrop) === null || _d === void 0 ? void 0 : _d.call(config, draggedItems, target);
79
+ (_b = config.onDrop) === null || _b === void 0 ? void 0 : _b.call(config, draggedItems, target);
70
80
  }
71
81
  else {
72
- (_e = config.onDropForeignDragObject) === null || _e === void 0 ? void 0 : _e.call(config, e.dataTransfer, target);
82
+ (_c = config.onDropForeignDragObject) === null || _c === void 0 ? void 0 : _c.call(config, e.dataTransfer, target);
73
83
  }
74
84
  // TODO rebuild tree?
75
- } }));
85
+ }) }));
76
86
  }, isDropTarget: () => {
77
87
  const target = tree.getDropTarget();
78
88
  return target ? target.item.getId() === item.getId() : false;
79
89
  }, isDropTargetAbove: () => {
80
90
  const target = tree.getDropTarget();
81
- if (!target || target.childIndex === null)
91
+ if (!target ||
92
+ target.childIndex === null ||
93
+ target.item !== item.getParent())
82
94
  return false;
83
- const targetIndex = target.item.getItemMeta().index;
84
- return targetIndex + target.childIndex + 1 === item.getItemMeta().index;
95
+ return target.childIndex === item.getItemMeta().posInSet;
85
96
  }, isDropTargetBelow: () => {
86
97
  const target = tree.getDropTarget();
87
- if (!target || target.childIndex === null)
98
+ if (!target ||
99
+ target.childIndex === null ||
100
+ target.item !== item.getParent())
88
101
  return false;
89
- const targetIndex = target.item.getItemMeta().index;
90
- return targetIndex + target.childIndex === item.getItemMeta().index;
102
+ return target.childIndex - 1 === item.getItemMeta().posInSet;
91
103
  }, isDraggingOver: () => {
92
104
  var _a, _b;
93
105
  return ((_b = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggingOverItem) === null || _b === void 0 ? void 0 : _b.getId()) === item.getId();
@@ -9,7 +9,12 @@ export type DndState<T> = {
9
9
  };
10
10
  export type DropTarget<T> = {
11
11
  item: ItemInstance<T>;
12
- childIndex: number | null;
12
+ childIndex: number;
13
+ insertionIndex: number;
14
+ } | {
15
+ item: ItemInstance<T>;
16
+ childIndex: null;
17
+ insertionIndex: null;
13
18
  };
14
19
  export declare enum DropTargetPosition {
15
20
  Top = "top",
@@ -35,6 +40,14 @@ export type DragAndDropFeatureDef<T> = {
35
40
  canDropForeignDragObject?: (dataTransfer: DataTransfer, target: DropTarget<T>) => boolean;
36
41
  onDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => void;
37
42
  onDropForeignDragObject?: (dataTransfer: DataTransfer, target: DropTarget<T>) => void;
43
+ /** Runs in the onDragEnd event, if `ev.dataTransfer.dropEffect` is not `none`, i.e. the drop
44
+ * was not aborted. No target is provided as parameter since the target may be a foreign drop target.
45
+ * This is useful to seperate out the logic to move dragged items out of their previous parents.
46
+ * Use `onDrop` to handle drop-related logic.
47
+ *
48
+ * This ignores the `canDrop` handler, since the drop target is unknown in this handler.
49
+ */
50
+ onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
38
51
  };
39
52
  treeInstance: {
40
53
  getDropTarget: () => DropTarget<T> | null;
@@ -3,4 +3,4 @@ import { DropTarget } from "./types";
3
3
  export declare const getDragCode: ({ item, childIndex }: DropTarget<any>) => string;
4
4
  export declare const getDropOffset: (e: any, item: ItemInstance<any>) => number;
5
5
  export declare const canDrop: (dataTransfer: DataTransfer | null, target: DropTarget<any>, tree: TreeInstance<any>) => boolean;
6
- export declare const getDropTarget: (e: any, item: ItemInstance<any>, tree: TreeInstance<any>) => DropTarget<any>;
6
+ export declare const getDropTarget: (e: any, item: ItemInstance<any>, tree: TreeInstance<any>, canDropInbetween?: boolean | undefined) => DropTarget<any>;
@@ -1,4 +1,4 @@
1
- import { DropTargetPosition } from "./types";
1
+ import { DropTargetPosition, } from "./types";
2
2
  export const getDragCode = ({ item, childIndex }) => `${item.getId()}__${childIndex !== null && childIndex !== void 0 ? childIndex : "none"}`;
3
3
  export const getDropOffset = (e, item) => {
4
4
  var _a;
@@ -28,28 +28,45 @@ const getDropTargetPosition = (offset, topLinePercentage, bottomLinePercentage)
28
28
  }
29
29
  return DropTargetPosition.Item;
30
30
  };
31
- export const getDropTarget = (e, item, tree) => {
32
- var _a, _b;
31
+ export const getDropTarget = (e, item, tree, canDropInbetween = tree.getConfig().canDropInbetween) => {
32
+ var _a, _b, _c, _d;
33
33
  const config = tree.getConfig();
34
- const offset = getDropOffset(e, item);
35
- const dropOnItemTarget = { item, childIndex: null };
36
- const pos = getDropTargetPosition(offset, (_a = config.topLinePercentage) !== null && _a !== void 0 ? _a : 0.3, (_b = config.bottomLinePercentage) !== null && _b !== void 0 ? _b : 0.7);
37
- const inbetweenPos = getDropTargetPosition(offset, 0.5, 0.5);
38
- if (!config.canDropInbetween) {
39
- return dropOnItemTarget;
40
- }
41
- if (!canDrop(e.dataTransfer, dropOnItemTarget, tree)) {
42
- return {
43
- item: item.getParent(),
44
- childIndex: item.getIndexInParent() +
45
- (inbetweenPos === DropTargetPosition.Top ? 0 : 1),
46
- };
34
+ const draggedItems = (_b = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems) !== null && _b !== void 0 ? _b : [];
35
+ const itemTarget = { item, childIndex: null, insertionIndex: null };
36
+ const parentTarget = {
37
+ item: item.getParent(),
38
+ childIndex: null,
39
+ insertionIndex: null,
40
+ };
41
+ if (!canDropInbetween) {
42
+ if (!canDrop(e.dataTransfer, parentTarget, tree)) {
43
+ return getDropTarget(e, item.getParent(), tree, false);
44
+ }
45
+ return itemTarget;
47
46
  }
47
+ const canDropInside = canDrop(e.dataTransfer, itemTarget, tree);
48
+ const offset = getDropOffset(e, item);
49
+ const pos = canDropInside
50
+ ? getDropTargetPosition(offset, (_c = config.topLinePercentage) !== null && _c !== void 0 ? _c : 0.3, (_d = config.bottomLinePercentage) !== null && _d !== void 0 ? _d : 0.7)
51
+ : getDropTargetPosition(offset, 0.5, 0.5);
48
52
  if (pos === DropTargetPosition.Item) {
49
- return dropOnItemTarget;
53
+ return itemTarget;
54
+ }
55
+ if (!canDrop(e.dataTransfer, parentTarget, tree)) {
56
+ return getDropTarget(e, item.getParent(), tree, false);
50
57
  }
58
+ const childIndex = item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1);
59
+ const numberOfDragItemsBeforeTarget = item
60
+ .getParent()
61
+ .getChildren()
62
+ .slice(0, childIndex)
63
+ .reduce((counter, child) => child && (draggedItems === null || draggedItems === void 0 ? void 0 : draggedItems.some((i) => i.getId() === child.getId()))
64
+ ? ++counter
65
+ : counter, 0);
51
66
  return {
52
67
  item: item.getParent(),
53
- childIndex: item.getIndexInParent() + (pos === DropTargetPosition.Top ? 0 : 1),
68
+ childIndex,
69
+ // TODO performance could be improved by computing this only when dragcode changed
70
+ insertionIndex: childIndex - numberOfDragItemsBeforeTarget,
54
71
  };
55
72
  };
@@ -13,24 +13,27 @@ export const expandAllFeature = {
13
13
  dependingFeatures: ["main", "tree"],
14
14
  createTreeInstance: (prev, tree) => (Object.assign(Object.assign({}, prev), { expandAll: (cancelToken) => __awaiter(void 0, void 0, void 0, function* () {
15
15
  yield Promise.all(tree.getItems().map((item) => item.expandAll(cancelToken)));
16
- }), collapseAll: () => __awaiter(void 0, void 0, void 0, function* () {
17
- var _a, _b;
18
- (_b = (_a = tree.getConfig()).setExpandedItems) === null || _b === void 0 ? void 0 : _b.call(_a, []);
19
- }) })),
16
+ }), collapseAll: () => {
17
+ tree.applySubStateUpdate("expandedItems", []);
18
+ tree.rebuildTree();
19
+ } })),
20
20
  createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { expandAll: (cancelToken) => __awaiter(void 0, void 0, void 0, function* () {
21
21
  if (cancelToken === null || cancelToken === void 0 ? void 0 : cancelToken.current) {
22
22
  return;
23
23
  }
24
+ if (!item.isFolder()) {
25
+ return;
26
+ }
24
27
  item.expand();
25
28
  yield poll(() => !tree.getState().loadingItems.includes(item.getId()));
26
29
  yield Promise.all(item.getChildren().map((child) => __awaiter(void 0, void 0, void 0, function* () {
27
30
  yield poll(() => !tree.getState().loadingItems.includes(child.getId()));
28
31
  yield (child === null || child === void 0 ? void 0 : child.expandAll(cancelToken));
29
32
  })));
30
- }), collapseAll: () => __awaiter(void 0, void 0, void 0, function* () {
31
- yield Promise.all(item.getChildren().map((child) => __awaiter(void 0, void 0, void 0, function* () {
32
- yield (child === null || child === void 0 ? void 0 : child.collapseAll());
33
- })));
33
+ }), collapseAll: () => {
34
+ for (const child of item.getChildren()) {
35
+ child === null || child === void 0 ? void 0 : child.collapseAll();
36
+ }
34
37
  item.collapse();
35
- }) })),
38
+ } })),
36
39
  };
@@ -5,13 +5,13 @@ export type ExpandAllFeatureDef = {
5
5
  expandAll: (cancelToken?: {
6
6
  current: boolean;
7
7
  }) => Promise<void>;
8
- collapseAll: () => Promise<void>;
8
+ collapseAll: () => void;
9
9
  };
10
10
  itemInstance: {
11
11
  expandAll: (cancelToken?: {
12
12
  current: boolean;
13
13
  }) => Promise<void>;
14
- collapseAll: () => Promise<void>;
14
+ collapseAll: () => void;
15
15
  };
16
16
  hotkeys: never;
17
17
  };
@@ -1,13 +1,16 @@
1
- import { FeatureImplementation, HotkeysConfig, ItemInstance, SetStateFn, TreeConfig, TreeState } from "../../types/core";
1
+ import { FeatureImplementation, HotkeysConfig, ItemInstance, SetStateFn, TreeConfig, TreeState, Updater } from "../../types/core";
2
2
  import { ItemMeta } from "../tree/types";
3
3
  export type MainFeatureDef<T = any> = {
4
4
  state: {};
5
5
  config: {
6
6
  features?: FeatureImplementation<any>[];
7
+ initialState?: Partial<TreeState<T>>;
7
8
  state?: Partial<TreeState<T>>;
8
9
  setState?: SetStateFn<TreeState<T>>;
9
10
  };
10
11
  treeInstance: {
12
+ /** @internal */
13
+ applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
11
14
  setState: SetStateFn<TreeState<T>>;
12
15
  getState: () => TreeState<T>;
13
16
  setConfig: SetStateFn<TreeConfig<T>>;
@@ -3,36 +3,37 @@ export const renamingFeature = {
3
3
  key: "renaming",
4
4
  dependingFeatures: ["main", "tree"],
5
5
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setRenamingItem: makeStateUpdater("renamingItem", tree), setRenamingValue: makeStateUpdater("renamingValue", tree), canRename: () => true }, defaultConfig)),
6
+ stateHandlerNames: {
7
+ renamingItem: "setRenamingItem",
8
+ renamingValue: "setRenamingValue",
9
+ },
6
10
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { startRenamingItem: (itemId) => {
7
- var _a, _b;
8
11
  const config = instance.getConfig();
9
12
  const item = instance.getItemInstance(itemId);
10
13
  if (!item.canRename()) {
11
14
  return;
12
15
  }
13
- (_a = config.setRenamingItem) === null || _a === void 0 ? void 0 : _a.call(config, itemId);
14
- (_b = config.setRenamingValue) === null || _b === void 0 ? void 0 : _b.call(config, item.getItemName());
16
+ instance.applySubStateUpdate("renamingItem", itemId);
17
+ instance.applySubStateUpdate("renamingValue", item.getItemName());
15
18
  }, getRenamingItem: () => {
16
19
  const itemId = instance.getState().renamingItem;
17
20
  return itemId ? instance.getItemInstance(itemId) : null;
18
21
  }, getRenamingValue: () => instance.getState().renamingValue || "", abortRenaming: () => {
19
- var _a, _b;
20
- (_b = (_a = instance.getConfig()).setRenamingItem) === null || _b === void 0 ? void 0 : _b.call(_a, null);
22
+ instance.applySubStateUpdate("renamingItem", null);
21
23
  }, completeRenaming: () => {
22
- var _a, _b, _c;
24
+ var _a;
23
25
  const config = instance.getConfig();
24
26
  const item = instance.getRenamingItem();
25
27
  if (item) {
26
28
  (_a = config.onRename) === null || _a === void 0 ? void 0 : _a.call(config, item, instance.getState().renamingValue || "");
27
29
  }
28
- (_c = (_b = instance.getConfig()).setRenamingItem) === null || _c === void 0 ? void 0 : _c.call(_b, null);
30
+ instance.applySubStateUpdate("renamingItem", null);
29
31
  }, isRenamingItem: () => !!instance.getState().renamingItem })),
30
32
  createItemInstance: (prev, instance, tree) => (Object.assign(Object.assign({}, prev), { getRenameInputProps: () => ({
31
33
  onBlur: () => tree.abortRenaming(),
32
34
  value: tree.getRenamingValue(),
33
35
  onChange: (e) => {
34
- var _a, _b;
35
- (_b = (_a = tree.getConfig()).setRenamingValue) === null || _b === void 0 ? void 0 : _b.call(_a, e.target.value);
36
+ tree.applySubStateUpdate("renamingValue", e.target.value);
36
37
  },
37
38
  }), canRename: () => { var _a, _b, _c; return (_c = (_b = (_a = tree.getConfig()).canRename) === null || _b === void 0 ? void 0 : _b.call(_a, instance)) !== null && _c !== void 0 ? _c : true; }, isRenaming: () => instance.getId() === tree.getState().renamingItem })),
38
39
  hotkeys: {
@@ -5,16 +5,19 @@ export const searchFeature = {
5
5
  getInitialState: (initialState) => (Object.assign({ search: null }, initialState)),
6
6
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setSearch: makeStateUpdater("search", tree), isSearchMatchingItem: (search, item) => search.length > 0 &&
7
7
  item.getItemName().toLowerCase().includes(search.toLowerCase()) }, defaultConfig)),
8
+ stateHandlerNames: {
9
+ search: "setSearch",
10
+ },
8
11
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { setSearch: (search) => {
9
- var _a, _b, _c;
10
- (_b = (_a = instance.getConfig()).setSearch) === null || _b === void 0 ? void 0 : _b.call(_a, search);
11
- (_c = instance
12
+ var _a;
13
+ instance.applySubStateUpdate("search", search);
14
+ (_a = instance
12
15
  .getItems()
13
16
  .find((item) => {
14
17
  var _a, _b;
15
18
  return (_b = (_a = instance
16
19
  .getConfig()).isSearchMatchingItem) === null || _b === void 0 ? void 0 : _b.call(_a, instance.getSearchValue(), item);
17
- })) === null || _c === void 0 ? void 0 : _c.setFocused();
20
+ })) === null || _a === void 0 ? void 0 : _a.setFocused();
18
21
  }, openSearch: (initialValue = "") => {
19
22
  instance.setSearch(initialValue);
20
23
  setTimeout(() => {
@@ -36,7 +39,7 @@ export const searchFeature = {
36
39
  value: instance.getSearchValue(),
37
40
  onChange: (e) => instance.setSearch(e.target.value),
38
41
  onBlur: () => instance.closeSearch(),
39
- }), getSearchMatchingItems: memo((search, items) => items.filter((item) => { var _a, _b; return (_b = (_a = instance.getConfig()).isSearchMatchingItem) === null || _b === void 0 ? void 0 : _b.call(_a, search, item); }), () => [instance.getSearchValue(), instance.getItems()]) })),
42
+ }), getSearchMatchingItems: memo((search, items) => items.filter((item) => { var _a, _b; return search && ((_b = (_a = instance.getConfig()).isSearchMatchingItem) === null || _b === void 0 ? void 0 : _b.call(_a, search, item)); }), () => [instance.getSearchValue(), instance.getItems()]) })),
40
43
  createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { isMatchingSearch: () => tree.getSearchMatchingItems().some((i) => i.getId() === item.getId()) })),
41
44
  hotkeys: {
42
45
  openSearch: {
@@ -57,30 +60,41 @@ export const searchFeature = {
57
60
  tree.closeSearch();
58
61
  },
59
62
  },
63
+ submitSearch: {
64
+ hotkey: "Enter",
65
+ allowWhenInputFocused: true,
66
+ isEnabled: (tree) => tree.isSearchOpen(),
67
+ handler: (e, tree) => {
68
+ tree.closeSearch();
69
+ tree.setSelectedItems([tree.getFocusedItem().getId()]);
70
+ },
71
+ },
60
72
  nextSearchItem: {
61
73
  hotkey: "ArrowDown",
62
74
  allowWhenInputFocused: true,
75
+ canRepeat: true,
63
76
  isEnabled: (tree) => tree.isSearchOpen(),
64
77
  handler: (e, tree) => {
65
- // TODO scroll into view
66
78
  const focusItem = tree
67
79
  .getSearchMatchingItems()
68
80
  .find((item) => item.getItemMeta().index >
69
81
  tree.getFocusedItem().getItemMeta().index);
70
82
  focusItem === null || focusItem === void 0 ? void 0 : focusItem.setFocused();
83
+ focusItem === null || focusItem === void 0 ? void 0 : focusItem.scrollTo({ block: "nearest", inline: "nearest" });
71
84
  },
72
85
  },
73
86
  previousSearchItem: {
74
87
  hotkey: "ArrowUp",
75
88
  allowWhenInputFocused: true,
89
+ canRepeat: true,
76
90
  isEnabled: (tree) => tree.isSearchOpen(),
77
91
  handler: (e, tree) => {
78
- // TODO scroll into view
79
92
  const focusItem = [...tree.getSearchMatchingItems()]
80
93
  .reverse()
81
94
  .find((item) => item.getItemMeta().index <
82
95
  tree.getFocusedItem().getItemMeta().index);
83
96
  focusItem === null || focusItem === void 0 ? void 0 : focusItem.setFocused();
97
+ focusItem === null || focusItem === void 0 ? void 0 : focusItem.scrollTo({ block: "nearest", inline: "nearest" });
84
98
  },
85
99
  },
86
100
  },
@@ -29,5 +29,5 @@ export type SearchFeatureDef<T> = {
29
29
  itemInstance: {
30
30
  isMatchingSearch: () => boolean;
31
31
  };
32
- hotkeys: "openSearch" | "closeSearch" | "nextSearchItem" | "previousSearchItem";
32
+ hotkeys: "openSearch" | "closeSearch" | "submitSearch" | "nextSearchItem" | "previousSearchItem";
33
33
  };
@@ -4,9 +4,11 @@ export const selectionFeature = {
4
4
  dependingFeatures: ["main", "tree"],
5
5
  getInitialState: (initialState) => (Object.assign({ selectedItems: [] }, initialState)),
6
6
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setSelectedItems: makeStateUpdater("selectedItems", tree) }, defaultConfig)),
7
+ stateHandlerNames: {
8
+ selectedItems: "setSelectedItems",
9
+ },
7
10
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { setSelectedItems: (selectedItems) => {
8
- var _a, _b;
9
- (_b = (_a = instance.getConfig()).setSelectedItems) === null || _b === void 0 ? void 0 : _b.call(_a, selectedItems);
11
+ instance.applySubStateUpdate("selectedItems", selectedItems);
10
12
  },
11
13
  // TODO memo
12
14
  getSelectedItems: () => {
@@ -48,7 +50,7 @@ export const selectionFeature = {
48
50
  else {
49
51
  item.select();
50
52
  }
51
- }, getProps: () => (Object.assign(Object.assign({}, prev.getProps()), { onClick: (e) => {
53
+ }, getProps: () => (Object.assign(Object.assign({}, prev.getProps()), { "aria-selected": item.isSelected() ? "true" : "false", onClick: item.getMemoizedProp("selection/onClick", () => (e) => {
52
54
  var _a, _b;
53
55
  if (e.shiftKey) {
54
56
  item.selectUpTo(e.ctrlKey || e.metaKey);
@@ -60,7 +62,7 @@ export const selectionFeature = {
60
62
  tree.setSelectedItems([item.getItemMeta().itemId]);
61
63
  }
62
64
  (_b = (_a = prev.getProps()).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
63
- } })) })),
65
+ }) })) })),
64
66
  hotkeys: {
65
67
  // setSelectedItem: {
66
68
  // hotkey: "space",
@@ -1,6 +1,12 @@
1
+ import { makeStateUpdater } from "../../utils";
1
2
  export const syncDataLoaderFeature = {
2
3
  key: "sync-data-loader",
3
4
  dependingFeatures: ["main"],
5
+ getInitialState: (initialState) => (Object.assign({ loadingItems: [] }, initialState)),
6
+ getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setLoadingItems: makeStateUpdater("loadingItems", tree) }, defaultConfig)),
7
+ stateHandlerNames: {
8
+ loadingItems: "setLoadingItems",
9
+ },
4
10
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: (itemId) => instance.getConfig().dataLoader.getItem(itemId), retrieveChildrenIds: (itemId) => instance.getConfig().dataLoader.getChildren(itemId) })),
5
11
  createItemInstance: (prev) => (Object.assign(Object.assign({}, prev), { isLoading: () => false })),
6
12
  };
@@ -6,7 +6,7 @@ export type SyncDataLoaderFeatureDef<T> = {
6
6
  state: {};
7
7
  config: {
8
8
  rootItemId: string;
9
- dataLoader: SyncTreeDataLoader<T>;
9
+ dataLoader?: SyncTreeDataLoader<T>;
10
10
  };
11
11
  treeInstance: {
12
12
  retrieveItemData: (itemId: string) => T;
@@ -13,6 +13,10 @@ export const treeFeature = {
13
13
  dependingFeatures: ["main"],
14
14
  getInitialState: (initialState) => (Object.assign({ expandedItems: [], focusedItem: null }, initialState)),
15
15
  getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setExpandedItems: makeStateUpdater("expandedItems", tree), setFocusedItem: makeStateUpdater("focusedItem", tree) }, defaultConfig)),
16
+ stateHandlerNames: {
17
+ expandedItems: "setExpandedItems",
18
+ focusedItem: "setFocusedItem",
19
+ },
16
20
  createTreeInstance: (prev, instance) => (Object.assign(Object.assign({}, prev), { retrieveItemData: () => {
17
21
  throw new Error("No data-loader registered");
18
22
  }, retrieveChildrenIds: () => {
@@ -20,7 +24,6 @@ export const treeFeature = {
20
24
  }, isItemExpanded: (itemId) => instance.getState().expandedItems.includes(itemId), getItemsMeta: () => {
21
25
  const { rootItemId } = instance.getConfig();
22
26
  const { expandedItems } = instance.getState();
23
- // console.log("!", instance.getConfig());
24
27
  const flatItems = [];
25
28
  const recursiveAdd = (itemId, parentId, level, setSize, posInSet) => {
26
29
  var _a;
@@ -47,23 +50,23 @@ export const treeFeature = {
47
50
  }
48
51
  return flatItems;
49
52
  }, expandItem: (itemId) => {
50
- var _a, _b, _c;
53
+ var _a;
51
54
  if (!instance.getItemInstance(itemId).isFolder()) {
52
55
  return;
53
56
  }
54
57
  if ((_a = instance.getState().loadingItems) === null || _a === void 0 ? void 0 : _a.includes(itemId)) {
55
58
  return;
56
59
  }
57
- (_c = (_b = instance
58
- .getConfig()).setExpandedItems) === null || _c === void 0 ? void 0 : _c.call(_b, (expandedItems) => [...expandedItems, itemId]);
60
+ instance.applySubStateUpdate("expandedItems", (expandedItems) => [
61
+ ...expandedItems,
62
+ itemId,
63
+ ]);
59
64
  instance.rebuildTree();
60
65
  }, collapseItem: (itemId) => {
61
- var _a, _b;
62
66
  if (!instance.getItemInstance(itemId).isFolder()) {
63
67
  return;
64
68
  }
65
- (_b = (_a = instance
66
- .getConfig()).setExpandedItems) === null || _b === void 0 ? void 0 : _b.call(_a, (expandedItems) => expandedItems.filter((id) => id !== itemId));
69
+ instance.applySubStateUpdate("expandedItems", (expandedItems) => expandedItems.filter((id) => id !== itemId));
67
70
  instance.rebuildTree();
68
71
  },
69
72
  // TODO memo
@@ -71,8 +74,7 @@ export const treeFeature = {
71
74
  var _a, _b;
72
75
  return ((_b = instance.getItemInstance((_a = instance.getState().focusedItem) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : instance.getItems()[0]);
73
76
  }, focusItem: (itemId) => {
74
- var _a, _b;
75
- (_b = (_a = instance.getConfig()).setFocusedItem) === null || _b === void 0 ? void 0 : _b.call(_a, itemId);
77
+ instance.applySubStateUpdate("focusedItem", itemId);
76
78
  }, focusNextItem: () => {
77
79
  const { index } = instance.getFocusedItem().getItemMeta();
78
80
  const nextIndex = Math.min(index + 1, instance.getItems().length - 1);
@@ -81,7 +83,7 @@ export const treeFeature = {
81
83
  const { index } = instance.getFocusedItem().getItemMeta();
82
84
  const nextIndex = Math.max(index - 1, 0);
83
85
  instance.focusItem(instance.getItems()[nextIndex].getId());
84
- }, updateDomFocus: (scrollIntoView) => {
86
+ }, updateDomFocus: () => {
85
87
  // Required because if the state is managed outside in react, the state only updated during next render
86
88
  setTimeout(() => __awaiter(void 0, void 0, void 0, function* () {
87
89
  var _a, _b;
@@ -92,9 +94,6 @@ export const treeFeature = {
92
94
  if (!focusedElement)
93
95
  return;
94
96
  focusedElement.focus();
95
- // if (scrollIntoView) {
96
- // focusedElement.scrollIntoView();
97
- // }
98
97
  }));
99
98
  }, getContainerProps: () => {
100
99
  var _a;
@@ -102,10 +101,15 @@ export const treeFeature = {
102
101
  } })),
103
102
  createItemInstance: (prev, item, tree) => (Object.assign(Object.assign({}, prev), { isLoading: () => {
104
103
  throw new Error("No data-loader registered");
105
- }, getId: () => item.getItemMeta().itemId, getProps: () => {
104
+ }, scrollTo: (scrollIntoViewArg) => __awaiter(void 0, void 0, void 0, function* () {
105
+ var _a, _b;
106
+ (_b = (_a = tree.getConfig()).scrollToItem) === null || _b === void 0 ? void 0 : _b.call(_a, item);
107
+ yield poll(() => item.getElement() !== null, 20);
108
+ item.getElement().scrollIntoView(scrollIntoViewArg);
109
+ }), getId: () => item.getItemMeta().itemId, getProps: () => {
106
110
  var _a;
107
111
  const itemMeta = item.getItemMeta();
108
- 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: (e) => {
112
+ 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) => {
109
113
  item.setFocused();
110
114
  item.primaryAction();
111
115
  if (e.ctrlKey || e.shiftKey || e.metaKey) {
@@ -120,7 +124,7 @@ export const treeFeature = {
120
124
  else {
121
125
  item.expand();
122
126
  }
123
- } });
127
+ }) });
124
128
  }, 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 ||
125
129
  (tree.getState().focusedItem === null && item.getItemMeta().index === 0), isFolder: () => item.getItemMeta().level === -1 ||
126
130
  tree.getConfig().isItemFolder(item), getItemName: () => {
@@ -134,14 +138,27 @@ export const treeFeature = {
134
138
  }
135
139
  }
136
140
  return tree.getItemInstance(tree.getConfig().rootItemId);
137
- }, () => [item.getItemMeta()]), getIndexInParent: () => {
138
- var _a, _b;
139
- return item.getItemMeta().index -
140
- ((_b = (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.getItemMeta().index) !== null && _b !== void 0 ? _b : 0) -
141
- 1;
142
- }, getChildren: () => tree
141
+ }, () => [item.getItemMeta()]),
142
+ // TODO remove
143
+ getIndexInParent: () => item.getItemMeta().posInSet, getChildren: () => tree
143
144
  .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] })),
145
+ .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) => {
146
+ var _a, _b, _c, _d, _e;
147
+ var _f, _g;
148
+ const data = item.getDataRef();
149
+ const memoizedValue = (_a = data.current.memoizedValues) === null || _a === void 0 ? void 0 : _a[name];
150
+ if (memoizedValue &&
151
+ (!deps ||
152
+ ((_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])))) {
153
+ return memoizedValue;
154
+ }
155
+ (_d = (_f = data.current).memoizedDeps) !== null && _d !== void 0 ? _d : (_f.memoizedDeps = {});
156
+ (_e = (_g = data.current).memoizedValues) !== null && _e !== void 0 ? _e : (_g.memoizedValues = {});
157
+ const value = create();
158
+ data.current.memoizedDeps[name] = deps;
159
+ data.current.memoizedValues[name] = value;
160
+ return value;
161
+ } })),
145
162
  hotkeys: {
146
163
  focusNextItem: {
147
164
  hotkey: "ArrowDown",
@@ -186,7 +203,7 @@ export const treeFeature = {
186
203
  if ((!item.isExpanded() || !item.isFolder()) &&
187
204
  item.getItemMeta().level !== 0) {
188
205
  (_a = item.getParent()) === null || _a === void 0 ? void 0 : _a.setFocused();
189
- tree.updateDomFocus(true);
206
+ tree.updateDomFocus();
190
207
  }
191
208
  else {
192
209
  item.collapse();