@headless-tree/core 0.0.0-20250322153940 → 0.0.0-20250330232313

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 (52) hide show
  1. package/CHANGELOG.md +2 -1
  2. package/lib/cjs/features/drag-and-drop/feature.js +11 -11
  3. package/lib/cjs/features/drag-and-drop/types.d.ts +11 -11
  4. package/lib/cjs/features/drag-and-drop/types.js +7 -7
  5. package/lib/cjs/features/drag-and-drop/utils.d.ts +18 -3
  6. package/lib/cjs/features/drag-and-drop/utils.js +42 -30
  7. package/lib/cjs/features/hotkeys-core/feature.js +1 -0
  8. package/lib/cjs/features/keyboard-drag-and-drop/feature.d.ts +2 -0
  9. package/lib/cjs/features/keyboard-drag-and-drop/feature.js +207 -0
  10. package/lib/cjs/features/keyboard-drag-and-drop/types.d.ts +27 -0
  11. package/lib/cjs/features/keyboard-drag-and-drop/types.js +11 -0
  12. package/lib/cjs/index.d.ts +2 -0
  13. package/lib/cjs/index.js +2 -0
  14. package/lib/cjs/mddocs-entry.d.ts +10 -0
  15. package/lib/cjs/test-utils/test-tree-expect.d.ts +5 -3
  16. package/lib/cjs/test-utils/test-tree-expect.js +3 -0
  17. package/lib/cjs/types/core.d.ts +2 -1
  18. package/lib/cjs/utilities/create-on-drop-handler.d.ts +2 -2
  19. package/lib/cjs/utilities/insert-items-at-target.d.ts +2 -2
  20. package/lib/esm/features/drag-and-drop/feature.js +12 -12
  21. package/lib/esm/features/drag-and-drop/types.d.ts +11 -11
  22. package/lib/esm/features/drag-and-drop/types.js +6 -6
  23. package/lib/esm/features/drag-and-drop/utils.d.ts +18 -3
  24. package/lib/esm/features/drag-and-drop/utils.js +37 -28
  25. package/lib/esm/features/hotkeys-core/feature.js +1 -0
  26. package/lib/esm/features/keyboard-drag-and-drop/feature.d.ts +2 -0
  27. package/lib/esm/features/keyboard-drag-and-drop/feature.js +204 -0
  28. package/lib/esm/features/keyboard-drag-and-drop/types.d.ts +27 -0
  29. package/lib/esm/features/keyboard-drag-and-drop/types.js +8 -0
  30. package/lib/esm/index.d.ts +2 -0
  31. package/lib/esm/index.js +2 -0
  32. package/lib/esm/mddocs-entry.d.ts +10 -0
  33. package/lib/esm/test-utils/test-tree-expect.d.ts +5 -3
  34. package/lib/esm/test-utils/test-tree-expect.js +3 -0
  35. package/lib/esm/types/core.d.ts +2 -1
  36. package/lib/esm/utilities/create-on-drop-handler.d.ts +2 -2
  37. package/lib/esm/utilities/insert-items-at-target.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/src/features/drag-and-drop/drag-and-drop.spec.ts +6 -6
  40. package/src/features/drag-and-drop/feature.ts +12 -12
  41. package/src/features/drag-and-drop/types.ts +11 -11
  42. package/src/features/drag-and-drop/utils.ts +64 -39
  43. package/src/features/hotkeys-core/feature.ts +1 -0
  44. package/src/features/keyboard-drag-and-drop/feature.ts +255 -0
  45. package/src/features/keyboard-drag-and-drop/keyboard-drag-and-drop.spec.ts +401 -0
  46. package/src/features/keyboard-drag-and-drop/types.ts +30 -0
  47. package/src/index.ts +2 -0
  48. package/src/mddocs-entry.ts +16 -0
  49. package/src/test-utils/test-tree-expect.ts +7 -2
  50. package/src/types/core.ts +2 -0
  51. package/src/utilities/create-on-drop-handler.ts +2 -2
  52. package/src/utilities/insert-items-at-target.ts +2 -2
@@ -1,15 +1,17 @@
1
1
  import { DragEvent } from "react";
2
2
  import { TestTree } from "./test-tree";
3
- import { DropTarget } from "../features/drag-and-drop/types";
3
+ import { DragTarget } from "../features/drag-and-drop/types";
4
+ import { TreeState } from "../types/core";
4
5
  export declare class TestTreeExpect<T> {
5
6
  private tree;
6
- protected itemInstance(itemId: string): import("..").ItemInstance<T>;
7
+ protected itemInstance(itemId: string): import("../types/core").ItemInstance<T>;
7
8
  protected itemProps(itemId: string): Record<string, any>;
8
9
  constructor(tree: TestTree<T>);
9
10
  foldersExpanded(...itemIds: string[]): void;
10
11
  foldersCollapsed(...itemIds: string[]): void;
11
12
  hasChildren(itemId: string, children: string[]): void;
12
- dropped(draggedItems: string[], target: DropTarget<any>): void;
13
+ substate<K extends keyof TreeState<T>>(key: K, value: TreeState<T>[K]): void;
14
+ dropped(draggedItems: string[], target: DragTarget<any>): void;
13
15
  dragOverNotAllowed(itemId: string, event?: DragEvent): DragEvent<Element>;
14
16
  defaultDragLineProps(indent?: number): void;
15
17
  }
@@ -29,6 +29,9 @@ class TestTreeExpect {
29
29
  const itemChildren = item.getChildren().map((child) => child.getId());
30
30
  (0, vitest_1.expect)(itemChildren).toEqual(children);
31
31
  }
32
+ substate(key, value) {
33
+ (0, vitest_1.expect)(this.tree.instance.getState()[key]).toEqual(value);
34
+ }
32
35
  dropped(draggedItems, target) {
33
36
  (0, vitest_1.expect)(this.tree.instance.getConfig().onDrop).toBeCalledWith(draggedItems.map((id) => this.tree.item(id)), target);
34
37
  }
@@ -9,6 +9,7 @@ import { SearchFeatureDef } from "../features/search/types";
9
9
  import { RenamingFeatureDef } from "../features/renaming/types";
10
10
  import { ExpandAllFeatureDef } from "../features/expand-all/types";
11
11
  import { PropMemoizationFeatureDef } from "../features/prop-memoization/types";
12
+ import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
12
13
  export type Updater<T> = T | ((old: T) => T);
13
14
  export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
14
15
  export type FeatureDef = {
@@ -33,7 +34,7 @@ type MergedFeatures<F extends FeatureDef> = {
33
34
  itemInstance: UnionToIntersection<F["itemInstance"]>;
34
35
  hotkeys: F["hotkeys"];
35
36
  };
36
- export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | DragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
37
+ export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
37
38
  type TreeStateType<T> = MergedFeatures<RegisteredFeatures<T>>["state"];
38
39
  export interface TreeState<T> extends TreeStateType<T> {
39
40
  }
@@ -1,3 +1,3 @@
1
1
  import { ItemInstance } from "../types/core";
2
- import { DropTarget } from "../features/drag-and-drop/types";
3
- export declare const createOnDropHandler: <T>(onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void) => (items: ItemInstance<T>[], target: DropTarget<T>) => Promise<void>;
2
+ import { DragTarget } from "../features/drag-and-drop/types";
3
+ export declare const createOnDropHandler: <T>(onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void) => (items: ItemInstance<T>[], target: DragTarget<T>) => Promise<void>;
@@ -1,3 +1,3 @@
1
1
  import { ItemInstance } from "../types/core";
2
- import { DropTarget } from "../features/drag-and-drop/types";
3
- export declare const insertItemsAtTarget: <T>(itemIds: string[], target: DropTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => Promise<void> | void) => Promise<void>;
2
+ import { DragTarget } from "../features/drag-and-drop/types";
3
+ export declare const insertItemsAtTarget: <T>(itemIds: string[], target: DragTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => Promise<void> | void) => Promise<void>;
@@ -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 { canDrop, getDragCode, getDropTarget } from "./utils";
10
+ import { canDrop, getDragCode, getDragTarget } from "./utils";
11
11
  import { makeStateUpdater } from "../../utils";
12
12
  export const dragAndDropFeature = {
13
13
  key: "drag-and-drop",
@@ -17,13 +17,13 @@ export const dragAndDropFeature = {
17
17
  dnd: "setDndState",
18
18
  },
19
19
  treeInstance: {
20
- getDropTarget: ({ tree }) => {
20
+ getDragTarget: ({ tree }) => {
21
21
  var _a, _b;
22
22
  return (_b = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.dragTarget) !== null && _b !== void 0 ? _b : null;
23
23
  },
24
24
  getDragLineData: ({ tree }) => {
25
25
  var _a, _b, _c, _d, _e, _f;
26
- const target = tree.getDropTarget();
26
+ const target = tree.getDragTarget();
27
27
  const indent = ((_a = target === null || target === void 0 ? void 0 : target.item.getItemMeta().level) !== null && _a !== void 0 ? _a : 0) + 1;
28
28
  const treeBb = (_b = tree.getElement()) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
29
29
  if (!target || !treeBb || !("childIndex" in target))
@@ -36,7 +36,7 @@ export const dragAndDropFeature = {
36
36
  if (bb) {
37
37
  return {
38
38
  indent,
39
- top: bb.bottom - treeBb.bottom,
39
+ top: bb.bottom - treeBb.top,
40
40
  left: bb.left + leftOffset - treeBb.left,
41
41
  width: bb.width - leftOffset,
42
42
  };
@@ -101,7 +101,7 @@ export const dragAndDropFeature = {
101
101
  return;
102
102
  }
103
103
  dataRef.current.lastDragCode = nextDragCode;
104
- const target = getDropTarget(e, item, tree);
104
+ const target = getDragTarget(e, item, tree);
105
105
  if (!((_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems) &&
106
106
  (!e.dataTransfer ||
107
107
  !((_c = (_b = tree
@@ -131,7 +131,7 @@ export const dragAndDropFeature = {
131
131
  }, onDrop: (e) => __awaiter(void 0, void 0, void 0, function* () {
132
132
  var _a, _b, _c;
133
133
  const dataRef = tree.getDataRef();
134
- const target = getDropTarget(e, item, tree);
134
+ const target = getDragTarget(e, item, tree);
135
135
  if (!canDrop(e.dataTransfer, target, tree)) {
136
136
  return;
137
137
  }
@@ -147,20 +147,20 @@ export const dragAndDropFeature = {
147
147
  yield ((_c = config.onDropForeignDragObject) === null || _c === void 0 ? void 0 : _c.call(config, e.dataTransfer, target));
148
148
  }
149
149
  }) })),
150
- isDropTarget: ({ tree, item }) => {
151
- const target = tree.getDropTarget();
150
+ isDragTarget: ({ tree, item }) => {
151
+ const target = tree.getDragTarget();
152
152
  return target ? target.item.getId() === item.getId() : false;
153
153
  },
154
- isDropTargetAbove: ({ tree, item }) => {
155
- const target = tree.getDropTarget();
154
+ isDragTargetAbove: ({ tree, item }) => {
155
+ const target = tree.getDragTarget();
156
156
  if (!target ||
157
157
  !("childIndex" in target) ||
158
158
  target.item !== item.getParent())
159
159
  return false;
160
160
  return target.childIndex === item.getItemMeta().posInSet;
161
161
  },
162
- isDropTargetBelow: ({ tree, item }) => {
163
- const target = tree.getDropTarget();
162
+ isDragTargetBelow: ({ tree, item }) => {
163
+ const target = tree.getDragTarget();
164
164
  if (!target ||
165
165
  !("childIndex" in target) ||
166
166
  target.item !== item.getParent())
@@ -6,7 +6,7 @@ export interface DndDataRef {
6
6
  export interface DndState<T> {
7
7
  draggedItems?: ItemInstance<T>[];
8
8
  draggingOverItem?: ItemInstance<T>;
9
- dragTarget?: DropTarget<T>;
9
+ dragTarget?: DragTarget<T>;
10
10
  }
11
11
  export interface DragLineData {
12
12
  indent: number;
@@ -14,7 +14,7 @@ export interface DragLineData {
14
14
  left: number;
15
15
  width: number;
16
16
  }
17
- export type DropTarget<T> = {
17
+ export type DragTarget<T> = {
18
18
  item: ItemInstance<T>;
19
19
  childIndex: number;
20
20
  insertionIndex: number;
@@ -23,7 +23,7 @@ export type DropTarget<T> = {
23
23
  } | {
24
24
  item: ItemInstance<T>;
25
25
  };
26
- export declare enum DropTargetPosition {
26
+ export declare enum DragTargetPosition {
27
27
  Top = "top",
28
28
  Bottom = "bottom",
29
29
  Item = "item"
@@ -40,26 +40,26 @@ export type DragAndDropFeatureDef<T> = {
40
40
  reorderAreaPercentage?: number;
41
41
  canReorder?: boolean;
42
42
  canDrag?: (items: ItemInstance<T>[]) => boolean;
43
- canDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => boolean;
43
+ canDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => boolean;
44
44
  indent?: number;
45
45
  createForeignDragObject?: (items: ItemInstance<T>[]) => {
46
46
  format: string;
47
47
  data: any;
48
48
  };
49
- canDropForeignDragObject?: (dataTransfer: DataTransfer, target: DropTarget<T>) => boolean;
50
- onDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => void | Promise<void>;
51
- onDropForeignDragObject?: (dataTransfer: DataTransfer, target: DropTarget<T>) => void | Promise<void>;
49
+ canDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => boolean;
50
+ onDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => void | Promise<void>;
51
+ onDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => void | Promise<void>;
52
52
  onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void;
53
53
  };
54
54
  treeInstance: {
55
- getDropTarget: () => DropTarget<T> | null;
55
+ getDragTarget: () => DragTarget<T> | null;
56
56
  getDragLineData: () => DragLineData | null;
57
57
  getDragLineStyle: (topOffset?: number, leftOffset?: number) => Record<string, any>;
58
58
  };
59
59
  itemInstance: {
60
- isDropTarget: () => boolean;
61
- isDropTargetAbove: () => boolean;
62
- isDropTargetBelow: () => boolean;
60
+ isDragTarget: () => boolean;
61
+ isDragTargetAbove: () => boolean;
62
+ isDragTargetBelow: () => boolean;
63
63
  isDraggingOver: () => boolean;
64
64
  };
65
65
  hotkeys: never;
@@ -1,6 +1,6 @@
1
- export var DropTargetPosition;
2
- (function (DropTargetPosition) {
3
- DropTargetPosition["Top"] = "top";
4
- DropTargetPosition["Bottom"] = "bottom";
5
- DropTargetPosition["Item"] = "item";
6
- })(DropTargetPosition || (DropTargetPosition = {}));
1
+ export var DragTargetPosition;
2
+ (function (DragTargetPosition) {
3
+ DragTargetPosition["Top"] = "top";
4
+ DragTargetPosition["Bottom"] = "bottom";
5
+ DragTargetPosition["Item"] = "item";
6
+ })(DragTargetPosition || (DragTargetPosition = {}));
@@ -1,5 +1,20 @@
1
1
  import { ItemInstance, TreeInstance } from "../../types/core";
2
- import { DropTarget } from "./types";
3
- export declare const canDrop: (dataTransfer: DataTransfer | null, target: DropTarget<any>, tree: TreeInstance<any>) => boolean;
2
+ import { DragTarget } from "./types";
3
+ export declare enum ItemDropCategory {
4
+ Item = 0,
5
+ ExpandedFolder = 1,
6
+ LastInGroup = 2
7
+ }
8
+ export declare const canDrop: (dataTransfer: DataTransfer | null, target: DragTarget<any>, tree: TreeInstance<any>) => boolean;
9
+ export declare const getItemDropCategory: (item: ItemInstance<any>) => ItemDropCategory;
10
+ export declare const getInsertionIndex: <T>(children: ItemInstance<T>[], childIndex: number, draggedItems: ItemInstance<T>[] | undefined) => number;
4
11
  export declare const getDragCode: (e: any, item: ItemInstance<any>, tree: TreeInstance<any>) => string;
5
- export declare const getDropTarget: (e: any, item: ItemInstance<any>, tree: TreeInstance<any>, canReorder?: boolean | undefined) => DropTarget<any>;
12
+ /** @param item refers to the bottom-most item of the container, at which bottom is being reparented on (e.g. root-1-2-6) */
13
+ export declare const getReparentTarget: <T>(item: ItemInstance<T>, reparentLevel: number, draggedItems: ItemInstance<T>[] | undefined) => {
14
+ item: ItemInstance<any>;
15
+ childIndex: number;
16
+ insertionIndex: number;
17
+ dragLineIndex: number;
18
+ dragLineLevel: number;
19
+ };
20
+ export declare const getDragTarget: (e: any, item: ItemInstance<any>, tree: TreeInstance<any>, canReorder?: boolean | undefined) => DragTarget<any>;
@@ -1,4 +1,4 @@
1
- var ItemDropCategory;
1
+ export var ItemDropCategory;
2
2
  (function (ItemDropCategory) {
3
3
  ItemDropCategory[ItemDropCategory["Item"] = 0] = "Item";
4
4
  ItemDropCategory[ItemDropCategory["ExpandedFolder"] = 1] = "ExpandedFolder";
@@ -12,7 +12,7 @@ var PlacementType;
12
12
  PlacementType[PlacementType["Reparent"] = 3] = "Reparent";
13
13
  })(PlacementType || (PlacementType = {}));
14
14
  export const canDrop = (dataTransfer, target, tree) => {
15
- var _a, _b, _c, _d;
15
+ var _a, _b, _c;
16
16
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
17
17
  const config = tree.getConfig();
18
18
  if (draggedItems && !((_c = (_b = config.canDrop) === null || _b === void 0 ? void 0 : _b.call(config, draggedItems, target)) !== null && _c !== void 0 ? _c : true)) {
@@ -25,12 +25,13 @@ export const canDrop = (dataTransfer, target, tree) => {
25
25
  }
26
26
  if (!draggedItems &&
27
27
  dataTransfer &&
28
- !((_d = config.canDropForeignDragObject) === null || _d === void 0 ? void 0 : _d.call(config, dataTransfer, target))) {
28
+ config.canDropForeignDragObject &&
29
+ !config.canDropForeignDragObject(dataTransfer, target)) {
29
30
  return false;
30
31
  }
31
32
  return true;
32
33
  };
33
- const getItemDropCategory = (item) => {
34
+ export const getItemDropCategory = (item) => {
34
35
  if (item.isExpanded()) {
35
36
  return ItemDropCategory.ExpandedFolder;
36
37
  }
@@ -40,6 +41,15 @@ const getItemDropCategory = (item) => {
40
41
  }
41
42
  return ItemDropCategory.Item;
42
43
  };
44
+ export const getInsertionIndex = (children, childIndex, draggedItems) => {
45
+ var _a;
46
+ const numberOfDragItemsBeforeTarget = (_a = children
47
+ .slice(0, childIndex)
48
+ .reduce((counter, child) => child && (draggedItems === null || draggedItems === void 0 ? void 0 : draggedItems.some((i) => i.getId() === child.getId()))
49
+ ? ++counter
50
+ : counter, 0)) !== null && _a !== void 0 ? _a : 0;
51
+ return childIndex - numberOfDragItemsBeforeTarget;
52
+ };
43
53
  const getTargetPlacement = (e, item, tree, canMakeChild) => {
44
54
  var _a, _b, _c, _d, _e;
45
55
  const config = tree.getConfig();
@@ -101,9 +111,24 @@ const getNthParent = (item, n) => {
101
111
  }
102
112
  return getNthParent(item.getParent(), n);
103
113
  };
104
- export const getDropTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) => {
105
- var _a, _b, _c;
106
- const draggedItems = (_b = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems) !== null && _b !== void 0 ? _b : [];
114
+ /** @param item refers to the bottom-most item of the container, at which bottom is being reparented on (e.g. root-1-2-6) */
115
+ export const getReparentTarget = (item, reparentLevel, draggedItems) => {
116
+ const itemMeta = item.getItemMeta();
117
+ const reparentedTarget = getNthParent(item, reparentLevel - 1);
118
+ const targetItemAbove = getNthParent(item, reparentLevel); // .getItemBelow()!;
119
+ const targetIndex = targetItemAbove.getIndexInParent() + 1;
120
+ // TODO possibly count items dragged out above the new target
121
+ return {
122
+ item: reparentedTarget,
123
+ childIndex: targetIndex,
124
+ insertionIndex: getInsertionIndex(reparentedTarget.getChildren(), targetIndex, draggedItems),
125
+ dragLineIndex: itemMeta.index + 1,
126
+ dragLineLevel: reparentLevel,
127
+ };
128
+ };
129
+ export const getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) => {
130
+ var _a;
131
+ const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
107
132
  const itemMeta = item.getItemMeta();
108
133
  const parent = item.getParent();
109
134
  const itemTarget = { item };
@@ -118,8 +143,8 @@ export const getDropTarget = (e, item, tree, canReorder = tree.getConfig().canRe
118
143
  return parentTarget;
119
144
  }
120
145
  if (!canReorder && parent && !canBecomeSibling) {
121
- // TODO! this breaks in story DND/Can Drop. Maybe move this logic into a composable DropTargetStrategy[] ?
122
- return getDropTarget(e, parent, tree, false);
146
+ // TODO! this breaks in story DND/Can Drop. Maybe move this logic into a composable DragTargetStrategy[] ?
147
+ return getDragTarget(e, parent, tree, false);
123
148
  }
124
149
  if (!parent) {
125
150
  // Shouldn't happen, but if dropped "next" to root item, just drop it inside
@@ -129,35 +154,19 @@ export const getDropTarget = (e, item, tree, canReorder = tree.getConfig().canRe
129
154
  return itemTarget;
130
155
  }
131
156
  if (!canBecomeSibling) {
132
- return getDropTarget(e, parent, tree, false);
157
+ return getDragTarget(e, parent, tree, false);
133
158
  }
134
159
  if (placement.type === PlacementType.Reparent) {
135
- const reparentedTarget = getNthParent(item, placement.reparentLevel - 1);
136
- const targetItemAbove = getNthParent(item, placement.reparentLevel); // .getItemBelow()!;
137
- const targetIndex = targetItemAbove.getIndexInParent() + 1;
138
- // TODO possibly count items dragged out above the new target
139
- return {
140
- item: reparentedTarget,
141
- childIndex: targetIndex,
142
- insertionIndex: targetIndex,
143
- dragLineIndex: itemMeta.index + 1,
144
- dragLineLevel: placement.reparentLevel,
145
- };
160
+ return getReparentTarget(item, placement.reparentLevel, draggedItems);
146
161
  }
147
162
  const maybeAddOneForBelow = placement.type === PlacementType.ReorderAbove ? 0 : 1;
148
163
  const childIndex = item.getIndexInParent() + maybeAddOneForBelow;
149
- const numberOfDragItemsBeforeTarget = (_c = parent
150
- .getChildren()
151
- .slice(0, childIndex)
152
- .reduce((counter, child) => child && (draggedItems === null || draggedItems === void 0 ? void 0 : draggedItems.some((i) => i.getId() === child.getId()))
153
- ? ++counter
154
- : counter, 0)) !== null && _c !== void 0 ? _c : 0;
155
164
  return {
156
165
  item: parent,
157
166
  dragLineIndex: itemMeta.index + maybeAddOneForBelow,
158
167
  dragLineLevel: itemMeta.level,
159
168
  childIndex,
160
169
  // TODO performance could be improved by computing this only when dragcode changed
161
- insertionIndex: childIndex - numberOfDragItemsBeforeTarget,
170
+ insertionIndex: getInsertionIndex(parent.getChildren(), childIndex, draggedItems),
162
171
  };
163
172
  };
@@ -27,6 +27,7 @@ export const hotkeysCoreFeature = {
27
27
  (_a = (_e = data.current).pressedKeys) !== null && _a !== void 0 ? _a : (_e.pressedKeys = new Set());
28
28
  const newMatch = !data.current.pressedKeys.has(e.key);
29
29
  data.current.pressedKeys.add(e.key);
30
+ console.log("HOTKEYS", data.current.pressedKeys);
30
31
  const hotkeyName = findHotkeyMatch(data.current.pressedKeys, tree, tree.getHotkeyPresets(), tree.getConfig().hotkeys);
31
32
  if (!hotkeyName)
32
33
  return;
@@ -0,0 +1,2 @@
1
+ import { FeatureImplementation } from "../../types/core";
2
+ export declare const keyboardDragAndDropFeature: FeatureImplementation;
@@ -0,0 +1,204 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { ItemDropCategory, canDrop, getInsertionIndex, getItemDropCategory, getReparentTarget, } from "../drag-and-drop/utils";
11
+ import { makeStateUpdater } from "../../utils";
12
+ import { AssistiveDndState } from "./types";
13
+ const getNextDragTarget = (tree, isUp, dragTarget) => {
14
+ var _a, _b, _c, _d;
15
+ const direction = isUp ? 0 : 1;
16
+ const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
17
+ // currently hovering between items
18
+ if ("childIndex" in dragTarget) {
19
+ // TODO move check in reusable function
20
+ const parent = dragTarget.item.getParent();
21
+ const targetedItem = tree.getItems()[dragTarget.dragLineIndex - 1]; // item above dragline
22
+ const targetCategory = targetedItem
23
+ ? getItemDropCategory(targetedItem)
24
+ : ItemDropCategory.Item;
25
+ const maxLevel = (_b = targetedItem === null || targetedItem === void 0 ? void 0 : targetedItem.getItemMeta().level) !== null && _b !== void 0 ? _b : 0;
26
+ const minLevel = (_d = (_c = targetedItem === null || targetedItem === void 0 ? void 0 : targetedItem.getItemBelow()) === null || _c === void 0 ? void 0 : _c.getItemMeta().level) !== null && _d !== void 0 ? _d : 0;
27
+ // reparenting
28
+ if (targetCategory === ItemDropCategory.LastInGroup) {
29
+ if (isUp && dragTarget.dragLineLevel < maxLevel) {
30
+ return getReparentTarget(targetedItem, dragTarget.dragLineLevel + 1, draggedItems);
31
+ }
32
+ if (!isUp && dragTarget.dragLineLevel > minLevel && parent) {
33
+ return getReparentTarget(targetedItem, dragTarget.dragLineLevel - 1, draggedItems);
34
+ }
35
+ }
36
+ const newIndex = dragTarget.dragLineIndex - 1 + direction;
37
+ const item = tree.getItems()[newIndex];
38
+ return item ? { item } : undefined;
39
+ }
40
+ // moving upwards outside of an open folder
41
+ const targetingExpandedFolder = getItemDropCategory(dragTarget.item) === ItemDropCategory.ExpandedFolder;
42
+ if (targetingExpandedFolder && !isUp) {
43
+ return {
44
+ item: dragTarget.item,
45
+ childIndex: 0,
46
+ insertionIndex: getInsertionIndex(dragTarget.item.getChildren(), 0, draggedItems),
47
+ dragLineIndex: dragTarget.item.getItemMeta().index + direction,
48
+ dragLineLevel: dragTarget.item.getItemMeta().level + 1,
49
+ };
50
+ }
51
+ // currently hovering over item
52
+ const childIndex = dragTarget.item.getIndexInParent() + direction;
53
+ return {
54
+ item: dragTarget.item.getParent(),
55
+ childIndex,
56
+ insertionIndex: getInsertionIndex(dragTarget.item.getParent().getChildren(), childIndex, draggedItems),
57
+ dragLineIndex: dragTarget.item.getItemMeta().index + direction,
58
+ dragLineLevel: dragTarget.item.getItemMeta().level,
59
+ };
60
+ };
61
+ const getNextValidDragTarget = (tree, isUp, previousTarget) => {
62
+ var _a, _b;
63
+ if (previousTarget === void 0) { previousTarget = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.dragTarget; }
64
+ if (!previousTarget)
65
+ return undefined;
66
+ const nextTarget = getNextDragTarget(tree, isUp, previousTarget);
67
+ const dataTransfer = (_b = tree.getDataRef().current.kDndDataTransfer) !== null && _b !== void 0 ? _b : null;
68
+ if (!nextTarget)
69
+ return undefined;
70
+ if (canDrop(dataTransfer, nextTarget, tree)) {
71
+ return nextTarget;
72
+ }
73
+ return getNextValidDragTarget(tree, isUp, nextTarget);
74
+ };
75
+ const updateScroll = (tree) => {
76
+ const state = tree.getState().dnd;
77
+ if (!(state === null || state === void 0 ? void 0 : state.dragTarget) || "childIndex" in state.dragTarget)
78
+ return;
79
+ state.dragTarget.item.scrollTo({ block: "nearest", inline: "nearest" });
80
+ };
81
+ const initiateDrag = (tree, draggedItems, dataTransfer) => {
82
+ var _a, _b;
83
+ const focusedItem = tree.getFocusedItem();
84
+ const { canDrag } = tree.getConfig();
85
+ if (draggedItems && canDrag && !canDrag(draggedItems)) {
86
+ return;
87
+ }
88
+ if (draggedItems) {
89
+ tree.applySubStateUpdate("dnd", { draggedItems });
90
+ // getNextValidDragTarget->canDrop needs the draggedItems in state
91
+ (_b = (_a = tree.getConfig()).onStartKeyboardDrag) === null || _b === void 0 ? void 0 : _b.call(_a, draggedItems);
92
+ }
93
+ else if (dataTransfer) {
94
+ tree.getDataRef().current.kDndDataTransfer = dataTransfer;
95
+ }
96
+ const dragTarget = getNextValidDragTarget(tree, false, {
97
+ item: focusedItem,
98
+ });
99
+ if (!dragTarget)
100
+ return;
101
+ tree.applySubStateUpdate("dnd", {
102
+ draggedItems,
103
+ dragTarget,
104
+ });
105
+ tree.applySubStateUpdate("assistiveDndState", AssistiveDndState.Started);
106
+ updateScroll(tree);
107
+ };
108
+ const moveDragPosition = (tree, isUp) => {
109
+ var _a;
110
+ const dragTarget = getNextValidDragTarget(tree, isUp);
111
+ if (!dragTarget)
112
+ return;
113
+ tree.applySubStateUpdate("dnd", {
114
+ draggedItems: (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems,
115
+ dragTarget,
116
+ });
117
+ tree.applySubStateUpdate("assistiveDndState", AssistiveDndState.Dragging);
118
+ if (!("childIndex" in dragTarget)) {
119
+ dragTarget.item.setFocused();
120
+ }
121
+ updateScroll(tree);
122
+ };
123
+ export const keyboardDragAndDropFeature = {
124
+ key: "keyboard-drag-and-drop",
125
+ deps: ["drag-and-drop"],
126
+ getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setAssistiveDndState: makeStateUpdater("assistiveDndState", tree) }, defaultConfig)),
127
+ stateHandlerNames: {
128
+ assistiveDndState: "setAssistiveDndState",
129
+ },
130
+ treeInstance: {
131
+ startKeyboardDrag: ({ tree }, draggedItems) => {
132
+ initiateDrag(tree, draggedItems, undefined);
133
+ },
134
+ startKeyboardDragOnForeignObject: ({ tree }, dataTransfer) => {
135
+ initiateDrag(tree, undefined, dataTransfer);
136
+ },
137
+ stopKeyboardDrag: ({ tree }) => {
138
+ tree.getDataRef().current.kDndDataTransfer = undefined;
139
+ tree.applySubStateUpdate("dnd", null);
140
+ tree.applySubStateUpdate("assistiveDndState", AssistiveDndState.None);
141
+ },
142
+ },
143
+ hotkeys: {
144
+ startDrag: {
145
+ hotkey: "Control+Shift+D",
146
+ preventDefault: true,
147
+ isEnabled: (tree) => !tree.getState().dnd,
148
+ handler: (_, tree) => {
149
+ tree.startKeyboardDrag(tree.getSelectedItems());
150
+ },
151
+ },
152
+ dragUp: {
153
+ hotkey: "ArrowUp",
154
+ preventDefault: true,
155
+ isEnabled: (tree) => !!tree.getState().dnd,
156
+ handler: (_, tree) => {
157
+ moveDragPosition(tree, true);
158
+ },
159
+ },
160
+ dragDown: {
161
+ hotkey: "ArrowDown",
162
+ preventDefault: true,
163
+ isEnabled: (tree) => !!tree.getState().dnd,
164
+ handler: (_, tree) => {
165
+ moveDragPosition(tree, false);
166
+ },
167
+ },
168
+ cancelDrag: {
169
+ hotkey: "Escape",
170
+ isEnabled: (tree) => !!tree.getState().dnd,
171
+ handler: (_, tree) => {
172
+ tree.stopKeyboardDrag();
173
+ },
174
+ },
175
+ completeDrag: {
176
+ hotkey: "Enter",
177
+ preventDefault: true,
178
+ isEnabled: (tree) => !!tree.getState().dnd,
179
+ handler: (e, tree) => __awaiter(void 0, void 0, void 0, function* () {
180
+ var _a, _b, _c, _d;
181
+ e.stopPropagation();
182
+ // TODO copied from keyboard onDrop, unify them
183
+ const dataRef = tree.getDataRef();
184
+ const target = tree.getDragTarget();
185
+ const dataTransfer = (_a = dataRef.current.kDndDataTransfer) !== null && _a !== void 0 ? _a : null;
186
+ if (!target || !canDrop(dataTransfer, target, tree)) {
187
+ return;
188
+ }
189
+ const config = tree.getConfig();
190
+ const draggedItems = (_b = tree.getState().dnd) === null || _b === void 0 ? void 0 : _b.draggedItems;
191
+ dataRef.current.lastDragCode = undefined;
192
+ tree.applySubStateUpdate("dnd", null);
193
+ if (draggedItems) {
194
+ yield ((_c = config.onDrop) === null || _c === void 0 ? void 0 : _c.call(config, draggedItems, target));
195
+ tree.getItemInstance(draggedItems[0].getId()).setFocused();
196
+ }
197
+ else if (dataTransfer) {
198
+ yield ((_d = config.onDropForeignDragObject) === null || _d === void 0 ? void 0 : _d.call(config, dataTransfer, target));
199
+ }
200
+ tree.applySubStateUpdate("assistiveDndState", AssistiveDndState.Completed);
201
+ }),
202
+ },
203
+ },
204
+ };
@@ -0,0 +1,27 @@
1
+ import { ItemInstance, SetStateFn } from "../../types/core";
2
+ export interface KDndDataRef {
3
+ kDndDataTransfer: DataTransfer | undefined;
4
+ }
5
+ export declare enum AssistiveDndState {
6
+ None = 0,
7
+ Started = 1,
8
+ Dragging = 2,
9
+ Completed = 3,
10
+ Aborted = 4
11
+ }
12
+ export type KeyboardDragAndDropFeatureDef<T> = {
13
+ state: {
14
+ assistiveDndState?: AssistiveDndState | null;
15
+ };
16
+ config: {
17
+ setAssistiveDndState?: SetStateFn<AssistiveDndState | undefined | null>;
18
+ onStartKeyboardDrag?: (items: ItemInstance<T>[]) => void;
19
+ };
20
+ treeInstance: {
21
+ startKeyboardDrag: (items: ItemInstance<T>[]) => void;
22
+ startKeyboardDragOnForeignObject: (dataTransfer: DataTransfer) => void;
23
+ stopKeyboardDrag: () => void;
24
+ };
25
+ itemInstance: {};
26
+ hotkeys: "startDrag" | "cancelDrag" | "completeDrag" | "dragUp" | "dragDown";
27
+ };
@@ -0,0 +1,8 @@
1
+ export var AssistiveDndState;
2
+ (function (AssistiveDndState) {
3
+ AssistiveDndState[AssistiveDndState["None"] = 0] = "None";
4
+ AssistiveDndState[AssistiveDndState["Started"] = 1] = "Started";
5
+ AssistiveDndState[AssistiveDndState["Dragging"] = 2] = "Dragging";
6
+ AssistiveDndState[AssistiveDndState["Completed"] = 3] = "Completed";
7
+ AssistiveDndState[AssistiveDndState["Aborted"] = 4] = "Aborted";
8
+ })(AssistiveDndState || (AssistiveDndState = {}));
@@ -3,6 +3,7 @@ export * from "./core/create-tree";
3
3
  export * from "./features/tree/types";
4
4
  export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
5
5
  export * from "./features/drag-and-drop/types";
6
+ export * from "./features/keyboard-drag-and-drop/types";
6
7
  export * from "./features/selection/types";
7
8
  export * from "./features/async-data-loader/types";
8
9
  export * from "./features/sync-data-loader/types";
@@ -16,6 +17,7 @@ export * from "./features/hotkeys-core/feature";
16
17
  export * from "./features/async-data-loader/feature";
17
18
  export * from "./features/sync-data-loader/feature";
18
19
  export * from "./features/drag-and-drop/feature";
20
+ export * from "./features/keyboard-drag-and-drop/feature";
19
21
  export * from "./features/search/feature";
20
22
  export * from "./features/renaming/feature";
21
23
  export * from "./features/expand-all/feature";