@headless-tree/core 0.0.0-20250716233347 → 0.0.0-20250723222210

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.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # @headless-tree/core
2
2
 
3
- ## 0.0.0-20250716233347
3
+ ## 0.0.0-20250723222210
4
4
 
5
5
  ### Patch Changes
6
6
 
7
+ - b41e1d2: fixed a bug where ending drag without successful drop doesn't properly reset drag line (#132)
7
8
  - b413f74: Fix `aria-posinset` and `aria-level` to be 1-based indexing
9
+ - a250b3b: Fix a bug where expand from the initial keyboard focus fails when rootItemId is an empty string
8
10
 
9
11
  ## 1.2.1
10
12
 
@@ -19,6 +19,19 @@ exports.dragAndDropFeature = {
19
19
  stateHandlerNames: {
20
20
  dnd: "setDndState",
21
21
  },
22
+ onTreeMount: (tree) => {
23
+ const listener = () => {
24
+ tree.applySubStateUpdate("dnd", null);
25
+ };
26
+ tree.getDataRef().current.windowDragEndListener = listener;
27
+ window.addEventListener("dragend", listener);
28
+ },
29
+ onTreeUnmount: (tree) => {
30
+ const { windowDragEndListener } = tree.getDataRef().current;
31
+ if (!windowDragEndListener)
32
+ return;
33
+ window.removeEventListener("dragend", windowDragEndListener);
34
+ },
22
35
  treeInstance: {
23
36
  getDragTarget: ({ tree }) => {
24
37
  var _a, _b;
@@ -84,7 +97,6 @@ exports.dragAndDropFeature = {
84
97
  const config = tree.getConfig();
85
98
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
86
99
  dataRef.current.lastDragCode = undefined;
87
- tree.applySubStateUpdate("dnd", null);
88
100
  if (draggedItems) {
89
101
  yield ((_b = config.onDrop) === null || _b === void 0 ? void 0 : _b.call(config, draggedItems, target));
90
102
  }
@@ -152,7 +164,6 @@ exports.dragAndDropFeature = {
152
164
  }, onDragEnd: (e) => {
153
165
  var _a, _b, _c, _d;
154
166
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
155
- tree.applySubStateUpdate("dnd", null);
156
167
  if (((_b = e.dataTransfer) === null || _b === void 0 ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
157
168
  return;
158
169
  }
@@ -169,7 +180,6 @@ exports.dragAndDropFeature = {
169
180
  const config = tree.getConfig();
170
181
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
171
182
  dataRef.current.lastDragCode = undefined;
172
- tree.applySubStateUpdate("dnd", null);
173
183
  if (draggedItems) {
174
184
  yield ((_b = config.onDrop) === null || _b === void 0 ? void 0 : _b.call(config, draggedItems, target));
175
185
  }
@@ -2,6 +2,7 @@ import { ItemInstance, SetStateFn } from "../../types/core";
2
2
  export interface DndDataRef {
3
3
  lastDragCode?: string;
4
4
  lastAllowDrop?: boolean;
5
+ windowDragEndListener?: () => void;
5
6
  }
6
7
  export interface DndState<T> {
7
8
  draggedItems?: ItemInstance<T>[];
@@ -56,8 +56,9 @@ exports.treeFeature = {
56
56
  return flatItems;
57
57
  },
58
58
  getFocusedItem: ({ tree }) => {
59
- var _a, _b;
60
- return ((_b = tree.getItemInstance((_a = tree.getState().focusedItem) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : tree.getItems()[0]);
59
+ var _a;
60
+ const focusedItemId = tree.getState().focusedItem;
61
+ return ((_a = (focusedItemId !== null ? tree.getItemInstance(focusedItemId) : null)) !== null && _a !== void 0 ? _a : tree.getItems()[0]);
61
62
  },
62
63
  getRootItem: ({ tree }) => {
63
64
  const { rootItemId } = tree.getConfig();
@@ -85,12 +85,14 @@ class TestTreeDo {
85
85
  dragEnd(itemId, event) {
86
86
  const e = event !== null && event !== void 0 ? event : test_tree_1.TestTree.dragEvent();
87
87
  this.itemProps(itemId).onDragEnd(e);
88
+ window.dispatchEvent(new CustomEvent("dragend"));
88
89
  return e;
89
90
  }
90
91
  drop(itemId, event) {
91
92
  return __awaiter(this, void 0, void 0, function* () {
92
93
  const e = event !== null && event !== void 0 ? event : test_tree_1.TestTree.dragEvent();
93
94
  yield this.itemProps(itemId).onDrop(e);
95
+ window.dispatchEvent(new CustomEvent("dragend"));
94
96
  return e;
95
97
  });
96
98
  }
@@ -127,6 +127,7 @@ class TestTree {
127
127
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
128
128
  this.instance;
129
129
  yield this.resolveAsyncVisibleItems();
130
+ this.instance.registerElement({ getBoundingClientRect: () => null });
130
131
  return this;
131
132
  });
132
133
  }
@@ -16,6 +16,19 @@ export const dragAndDropFeature = {
16
16
  stateHandlerNames: {
17
17
  dnd: "setDndState",
18
18
  },
19
+ onTreeMount: (tree) => {
20
+ const listener = () => {
21
+ tree.applySubStateUpdate("dnd", null);
22
+ };
23
+ tree.getDataRef().current.windowDragEndListener = listener;
24
+ window.addEventListener("dragend", listener);
25
+ },
26
+ onTreeUnmount: (tree) => {
27
+ const { windowDragEndListener } = tree.getDataRef().current;
28
+ if (!windowDragEndListener)
29
+ return;
30
+ window.removeEventListener("dragend", windowDragEndListener);
31
+ },
19
32
  treeInstance: {
20
33
  getDragTarget: ({ tree }) => {
21
34
  var _a, _b;
@@ -81,7 +94,6 @@ export const dragAndDropFeature = {
81
94
  const config = tree.getConfig();
82
95
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
83
96
  dataRef.current.lastDragCode = undefined;
84
- tree.applySubStateUpdate("dnd", null);
85
97
  if (draggedItems) {
86
98
  yield ((_b = config.onDrop) === null || _b === void 0 ? void 0 : _b.call(config, draggedItems, target));
87
99
  }
@@ -149,7 +161,6 @@ export const dragAndDropFeature = {
149
161
  }, onDragEnd: (e) => {
150
162
  var _a, _b, _c, _d;
151
163
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
152
- tree.applySubStateUpdate("dnd", null);
153
164
  if (((_b = e.dataTransfer) === null || _b === void 0 ? void 0 : _b.dropEffect) === "none" || !draggedItems) {
154
165
  return;
155
166
  }
@@ -166,7 +177,6 @@ export const dragAndDropFeature = {
166
177
  const config = tree.getConfig();
167
178
  const draggedItems = (_a = tree.getState().dnd) === null || _a === void 0 ? void 0 : _a.draggedItems;
168
179
  dataRef.current.lastDragCode = undefined;
169
- tree.applySubStateUpdate("dnd", null);
170
180
  if (draggedItems) {
171
181
  yield ((_b = config.onDrop) === null || _b === void 0 ? void 0 : _b.call(config, draggedItems, target));
172
182
  }
@@ -2,6 +2,7 @@ import { ItemInstance, SetStateFn } from "../../types/core";
2
2
  export interface DndDataRef {
3
3
  lastDragCode?: string;
4
4
  lastAllowDrop?: boolean;
5
+ windowDragEndListener?: () => void;
5
6
  }
6
7
  export interface DndState<T> {
7
8
  draggedItems?: ItemInstance<T>[];
@@ -53,8 +53,9 @@ export const treeFeature = {
53
53
  return flatItems;
54
54
  },
55
55
  getFocusedItem: ({ tree }) => {
56
- var _a, _b;
57
- return ((_b = tree.getItemInstance((_a = tree.getState().focusedItem) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : tree.getItems()[0]);
56
+ var _a;
57
+ const focusedItemId = tree.getState().focusedItem;
58
+ return ((_a = (focusedItemId !== null ? tree.getItemInstance(focusedItemId) : null)) !== null && _a !== void 0 ? _a : tree.getItems()[0]);
58
59
  },
59
60
  getRootItem: ({ tree }) => {
60
61
  const { rootItemId } = tree.getConfig();
@@ -82,12 +82,14 @@ export class TestTreeDo {
82
82
  dragEnd(itemId, event) {
83
83
  const e = event !== null && event !== void 0 ? event : TestTree.dragEvent();
84
84
  this.itemProps(itemId).onDragEnd(e);
85
+ window.dispatchEvent(new CustomEvent("dragend"));
85
86
  return e;
86
87
  }
87
88
  drop(itemId, event) {
88
89
  return __awaiter(this, void 0, void 0, function* () {
89
90
  const e = event !== null && event !== void 0 ? event : TestTree.dragEvent();
90
91
  yield this.itemProps(itemId).onDrop(e);
92
+ window.dispatchEvent(new CustomEvent("dragend"));
91
93
  return e;
92
94
  });
93
95
  }
@@ -124,6 +124,7 @@ export class TestTree {
124
124
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
125
125
  this.instance;
126
126
  yield this.resolveAsyncVisibleItems();
127
+ this.instance.registerElement({ getBoundingClientRect: () => null });
127
128
  return this;
128
129
  });
129
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-tree/core",
3
- "version": "0.0.0-20250716233347",
3
+ "version": "0.0.0-20250723222210",
4
4
  "main": "lib/cjs/index.js",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/esm/index.d.ts",
@@ -182,7 +182,7 @@ describe("core-feature/drag-and-drop", () => {
182
182
  });
183
183
  });
184
184
 
185
- it("updates dnd state", () => {
185
+ it("updates dnd state", async () => {
186
186
  const setDndState = tree.mockedHandler("setDndState");
187
187
  tree.do.startDrag("x111");
188
188
  expect(setDndState).toBeCalledWith({
@@ -198,7 +198,7 @@ describe("core-feature/drag-and-drop", () => {
198
198
  },
199
199
  });
200
200
  tree.do.drop("x22");
201
- expect(setDndState).toBeCalledWith(null);
201
+ await vi.waitFor(() => expect(setDndState).toBeCalledWith(null));
202
202
  });
203
203
  });
204
204
 
@@ -24,6 +24,19 @@ export const dragAndDropFeature: FeatureImplementation = {
24
24
  dnd: "setDndState",
25
25
  },
26
26
 
27
+ onTreeMount: (tree) => {
28
+ const listener = () => {
29
+ tree.applySubStateUpdate("dnd", null);
30
+ };
31
+ tree.getDataRef<DndDataRef>().current.windowDragEndListener = listener;
32
+ window.addEventListener("dragend", listener);
33
+ },
34
+ onTreeUnmount: (tree) => {
35
+ const { windowDragEndListener } = tree.getDataRef<DndDataRef>().current;
36
+ if (!windowDragEndListener) return;
37
+ window.removeEventListener("dragend", windowDragEndListener);
38
+ },
39
+
27
40
  treeInstance: {
28
41
  getDragTarget: ({ tree }) => {
29
42
  return tree.getState().dnd?.dragTarget ?? null;
@@ -106,7 +119,6 @@ export const dragAndDropFeature: FeatureImplementation = {
106
119
  const draggedItems = tree.getState().dnd?.draggedItems;
107
120
 
108
121
  dataRef.current.lastDragCode = undefined;
109
- tree.applySubStateUpdate("dnd", null);
110
122
 
111
123
  if (draggedItems) {
112
124
  await config.onDrop?.(draggedItems, target);
@@ -211,7 +223,6 @@ export const dragAndDropFeature: FeatureImplementation = {
211
223
 
212
224
  onDragEnd: (e: DragEvent) => {
213
225
  const draggedItems = tree.getState().dnd?.draggedItems;
214
- tree.applySubStateUpdate("dnd", null);
215
226
 
216
227
  if (e.dataTransfer?.dropEffect === "none" || !draggedItems) {
217
228
  return;
@@ -234,7 +245,6 @@ export const dragAndDropFeature: FeatureImplementation = {
234
245
  const draggedItems = tree.getState().dnd?.draggedItems;
235
246
 
236
247
  dataRef.current.lastDragCode = undefined;
237
- tree.applySubStateUpdate("dnd", null);
238
248
 
239
249
  if (draggedItems) {
240
250
  await config.onDrop?.(draggedItems, target);
@@ -3,6 +3,7 @@ import { ItemInstance, SetStateFn } from "../../types/core";
3
3
  export interface DndDataRef {
4
4
  lastDragCode?: string;
5
5
  lastAllowDrop?: boolean;
6
+ windowDragEndListener?: () => void;
6
7
  }
7
8
 
8
9
  export interface DndState<T> {
@@ -76,8 +76,9 @@ export const treeFeature: FeatureImplementation<any> = {
76
76
  },
77
77
 
78
78
  getFocusedItem: ({ tree }) => {
79
+ const focusedItemId = tree.getState().focusedItem;
79
80
  return (
80
- tree.getItemInstance(tree.getState().focusedItem ?? "") ??
81
+ (focusedItemId !== null ? tree.getItemInstance(focusedItemId) : null) ??
81
82
  tree.getItems()[0]
82
83
  );
83
84
  },
@@ -472,4 +472,14 @@ describe("core-feature/selections", () => {
472
472
  });
473
473
  });
474
474
  });
475
+
476
+ describe("empty rootItemId", () => {
477
+ factory.with({ rootItemId: "" }).forSuits((tree) => {
478
+ describe("focused item", () => {
479
+ it("returns correct initial focused item", () => {
480
+ expect(tree.instance.getFocusedItem().getId()).toBe("1");
481
+ });
482
+ });
483
+ });
484
+ });
475
485
  });
@@ -105,12 +105,14 @@ export class TestTreeDo<T> {
105
105
  dragEnd(itemId: string, event?: DragEvent) {
106
106
  const e = event ?? TestTree.dragEvent();
107
107
  this.itemProps(itemId).onDragEnd(e);
108
+ window.dispatchEvent(new CustomEvent("dragend"));
108
109
  return e;
109
110
  }
110
111
 
111
112
  async drop(itemId: string, event?: DragEvent) {
112
113
  const e = event ?? TestTree.dragEvent();
113
114
  await this.itemProps(itemId).onDrop(e);
115
+ window.dispatchEvent(new CustomEvent("dragend"));
114
116
  return e;
115
117
  }
116
118
 
@@ -137,6 +137,7 @@ export class TestTree<T = string> {
137
137
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
138
138
  this.instance;
139
139
  await this.resolveAsyncVisibleItems();
140
+ this.instance.registerElement({ getBoundingClientRect: () => null } as any);
140
141
  return this;
141
142
  }
142
143
 
package/vitest.config.ts CHANGED
@@ -2,5 +2,7 @@
2
2
  import { defineConfig } from "vitest/config";
3
3
 
4
4
  export default defineConfig({
5
- test: {},
5
+ test: {
6
+ environment: "jsdom",
7
+ },
6
8
  });