@headless-tree/core 0.0.0-20250915162508 → 0.0.0-20250918201417

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,15 @@
1
1
  # @headless-tree/core
2
2
 
3
- ## 0.0.0-20250915162508
3
+ ## 0.0.0-20250918201417
4
+
5
+ ### Minor Changes
6
+
7
+ - cbeaba6: all state updates (like setSelectedItems) will not propagate while the component is unmounted. This happened before for `tree.setState()` calls directly, but not individual state atoms like `setSelectedItems`. When calling `createTree()` directly (instead of `useTree()`), `tree.setMounted(true)` needs to be called once after mount. No changes are necessary when using the React-based `useTree()` integration. (#158)
4
8
 
5
9
  ### Patch Changes
6
10
 
7
11
  - 72e714b: all NPM deployments will now publish with provenance
12
+ - 6693986: fixed an issue where async data loaders cause calling `item.getItemData()` outside of the component calling `useTree()` to cause a React warning log (#158)
8
13
  - 7a7424f: fixed incorrect exports definition in package.json for require/cjs imports (#161)
9
14
  - 215ab4b: add a new symbol that can be used in hotkey configurations "metaorcontrol" that will trigger if either any windows control key or mac meta key is pressed (#141)
10
15
  - 51b0dea: Added `isUnorderedDragTarget` as alternative to `isDragTarget` for easier detection of drag type
package/dist/index.d.mts CHANGED
@@ -184,6 +184,8 @@ type MainFeatureDef<T = any> = {
184
184
  rebuildTree: () => void;
185
185
  /** @deprecated Experimental feature, might get removed or changed in the future. */
186
186
  scheduleRebuildTree: () => void;
187
+ /** @internal */
188
+ setMounted: (isMounted: boolean) => void;
187
189
  };
188
190
  itemInstance: {
189
191
  registerElement: (element: HTMLElement | null) => void;
package/dist/index.d.ts CHANGED
@@ -184,6 +184,8 @@ type MainFeatureDef<T = any> = {
184
184
  rebuildTree: () => void;
185
185
  /** @deprecated Experimental feature, might get removed or changed in the future. */
186
186
  scheduleRebuildTree: () => void;
187
+ /** @internal */
188
+ setMounted: (isMounted: boolean) => void;
187
189
  };
188
190
  itemInstance: {
189
191
  registerElement: (element: HTMLElement | null) => void;
package/dist/index.js CHANGED
@@ -544,10 +544,29 @@ var createTree = (initialConfig) => {
544
544
  var _a2;
545
545
  (_a2 = config.setState) == null ? void 0 : _a2.call(config, state);
546
546
  },
547
+ setMounted: ({}, isMounted) => {
548
+ var _a2;
549
+ const ref = treeDataRef;
550
+ treeDataRef.current.isMounted = isMounted;
551
+ if (isMounted) {
552
+ (_a2 = ref.waitingForMount) == null ? void 0 : _a2.forEach((cb) => cb());
553
+ ref.waitingForMount = [];
554
+ }
555
+ },
547
556
  applySubStateUpdate: ({}, stateName, updater) => {
548
- state[stateName] = typeof updater === "function" ? updater(state[stateName]) : updater;
549
- const externalStateSetter = config[stateHandlerNames[stateName]];
550
- externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]);
557
+ var _a2;
558
+ const apply = () => {
559
+ state[stateName] = typeof updater === "function" ? updater(state[stateName]) : updater;
560
+ const externalStateSetter = config[stateHandlerNames[stateName]];
561
+ externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]);
562
+ };
563
+ const ref = treeDataRef.current;
564
+ if (ref.isMounted) {
565
+ apply();
566
+ } else {
567
+ (_a2 = ref.waitingForMount) != null ? _a2 : ref.waitingForMount = [];
568
+ ref.waitingForMount.push(apply);
569
+ }
551
570
  },
552
571
  // TODO rebuildSubTree: (itemId: string) => void;
553
572
  rebuildTree: () => {
@@ -1169,7 +1188,7 @@ var asyncDataLoaderFeature = {
1169
1188
  return dataRef.current.itemData[itemId];
1170
1189
  }
1171
1190
  if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
1172
- loadItemData(tree, itemId);
1191
+ setTimeout(() => loadItemData(tree, itemId));
1173
1192
  }
1174
1193
  return (_b = (_a = config.createLoadingItemData) == null ? void 0 : _a.call(config)) != null ? _b : null;
1175
1194
  },
@@ -1181,7 +1200,7 @@ var asyncDataLoaderFeature = {
1181
1200
  if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
1182
1201
  return [];
1183
1202
  }
1184
- loadChildrenIds(tree, itemId);
1203
+ setTimeout(() => loadChildrenIds(tree, itemId));
1185
1204
  return [];
1186
1205
  }
1187
1206
  },
package/dist/index.mjs CHANGED
@@ -500,10 +500,29 @@ var createTree = (initialConfig) => {
500
500
  var _a2;
501
501
  (_a2 = config.setState) == null ? void 0 : _a2.call(config, state);
502
502
  },
503
+ setMounted: ({}, isMounted) => {
504
+ var _a2;
505
+ const ref = treeDataRef;
506
+ treeDataRef.current.isMounted = isMounted;
507
+ if (isMounted) {
508
+ (_a2 = ref.waitingForMount) == null ? void 0 : _a2.forEach((cb) => cb());
509
+ ref.waitingForMount = [];
510
+ }
511
+ },
503
512
  applySubStateUpdate: ({}, stateName, updater) => {
504
- state[stateName] = typeof updater === "function" ? updater(state[stateName]) : updater;
505
- const externalStateSetter = config[stateHandlerNames[stateName]];
506
- externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]);
513
+ var _a2;
514
+ const apply = () => {
515
+ state[stateName] = typeof updater === "function" ? updater(state[stateName]) : updater;
516
+ const externalStateSetter = config[stateHandlerNames[stateName]];
517
+ externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]);
518
+ };
519
+ const ref = treeDataRef.current;
520
+ if (ref.isMounted) {
521
+ apply();
522
+ } else {
523
+ (_a2 = ref.waitingForMount) != null ? _a2 : ref.waitingForMount = [];
524
+ ref.waitingForMount.push(apply);
525
+ }
507
526
  },
508
527
  // TODO rebuildSubTree: (itemId: string) => void;
509
528
  rebuildTree: () => {
@@ -1125,7 +1144,7 @@ var asyncDataLoaderFeature = {
1125
1144
  return dataRef.current.itemData[itemId];
1126
1145
  }
1127
1146
  if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
1128
- loadItemData(tree, itemId);
1147
+ setTimeout(() => loadItemData(tree, itemId));
1129
1148
  }
1130
1149
  return (_b = (_a = config.createLoadingItemData) == null ? void 0 : _a.call(config)) != null ? _b : null;
1131
1150
  },
@@ -1137,7 +1156,7 @@ var asyncDataLoaderFeature = {
1137
1156
  if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
1138
1157
  return [];
1139
1158
  }
1140
- loadChildrenIds(tree, itemId);
1159
+ setTimeout(() => loadChildrenIds(tree, itemId));
1141
1160
  return [];
1142
1161
  }
1143
1162
  },
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "checkbox",
14
14
  "hook"
15
15
  ],
16
- "version": "0.0.0-20250915162508",
16
+ "version": "0.0.0-20250918201417",
17
17
  "main": "dist/index.d.ts",
18
18
  "module": "dist/index.mjs",
19
19
  "types": "dist/index.d.mts",
@@ -11,6 +11,7 @@ import { treeFeature } from "../features/tree/feature";
11
11
  import { ItemMeta } from "../features/tree/types";
12
12
  import { buildStaticInstance } from "./build-static-instance";
13
13
  import { throwError } from "../utilities/errors";
14
+ import type { TreeDataRef } from "../features/main/types";
14
15
 
15
16
  const verifyFeatures = (features: FeatureImplementation[] | undefined) => {
16
17
  const loadedFeatures = features?.map((feature) => feature.key);
@@ -161,17 +162,34 @@ export const createTree = <T>(
161
162
  config.setState?.(state); // TODO this cant be right... This doesnt allow external state updates
162
163
  // TODO this is never used, remove
163
164
  },
165
+ setMounted: ({}, isMounted) => {
166
+ const ref = treeDataRef as TreeDataRef;
167
+ treeDataRef.current.isMounted = isMounted;
168
+ if (isMounted) {
169
+ ref.waitingForMount?.forEach((cb) => cb());
170
+ ref.waitingForMount = [];
171
+ }
172
+ },
164
173
  applySubStateUpdate: <K extends keyof TreeState<any>>(
165
174
  {},
166
175
  stateName: K,
167
176
  updater: Updater<TreeState<T>[K]>,
168
177
  ) => {
169
- state[stateName] =
170
- typeof updater === "function" ? updater(state[stateName]) : updater;
171
- const externalStateSetter = config[
172
- stateHandlerNames[stateName]
173
- ] as Function;
174
- externalStateSetter?.(state[stateName]);
178
+ const apply = () => {
179
+ state[stateName] =
180
+ typeof updater === "function" ? updater(state[stateName]) : updater;
181
+ const externalStateSetter = config[
182
+ stateHandlerNames[stateName]
183
+ ] as Function;
184
+ externalStateSetter?.(state[stateName]);
185
+ };
186
+ const ref = treeDataRef.current as TreeDataRef;
187
+ if (ref.isMounted) {
188
+ apply();
189
+ } else {
190
+ ref.waitingForMount ??= [];
191
+ ref.waitingForMount.push(apply);
192
+ }
175
193
  },
176
194
  // TODO rebuildSubTree: (itemId: string) => void;
177
195
  rebuildTree: () => {
@@ -46,6 +46,7 @@ describe("core-feature/selections", () => {
46
46
  );
47
47
  const setLoadingItemData = tree.mockedHandler("setLoadingItemData");
48
48
  tree.do.selectItem("x12");
49
+ await tree.do.awaitNextTick();
49
50
  expect(setLoadingItemChildrens).toHaveBeenCalledWith(["x12"]);
50
51
  expect(setLoadingItemData).not.toHaveBeenCalled();
51
52
  await tree.resolveAsyncVisibleItems();
@@ -118,7 +118,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
118
118
  }
119
119
 
120
120
  if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
121
- loadItemData(tree, itemId);
121
+ setTimeout(() => loadItemData(tree, itemId));
122
122
  }
123
123
 
124
124
  return config.createLoadingItemData?.() ?? null;
@@ -134,7 +134,7 @@ export const asyncDataLoaderFeature: FeatureImplementation = {
134
134
  return [];
135
135
  }
136
136
 
137
- loadChildrenIds(tree, itemId);
137
+ setTimeout(() => loadChildrenIds(tree, itemId));
138
138
 
139
139
  return [];
140
140
  },
@@ -10,6 +10,11 @@ import {
10
10
  } from "../../types/core";
11
11
  import { ItemMeta } from "../tree/types";
12
12
 
13
+ export interface TreeDataRef {
14
+ isMounted?: boolean;
15
+ waitingForMount?: (() => void)[];
16
+ }
17
+
13
18
  export type InstanceTypeMap = {
14
19
  itemInstance: ItemInstance<any>;
15
20
  treeInstance: TreeInstance<any>;
@@ -51,6 +56,8 @@ export type MainFeatureDef<T = any> = {
51
56
  rebuildTree: () => void;
52
57
  /** @deprecated Experimental feature, might get removed or changed in the future. */
53
58
  scheduleRebuildTree: () => void;
59
+ /** @internal */
60
+ setMounted: (isMounted: boolean) => void;
54
61
  };
55
62
  itemInstance: {
56
63
  registerElement: (element: HTMLElement | null) => void;
@@ -135,4 +135,10 @@ export class TestTreeDo<T> {
135
135
  "function called with inconsistent parameters",
136
136
  ).toBeOneOf([0, 1]);
137
137
  }
138
+
139
+ async awaitNextTick() {
140
+ await new Promise((r) => {
141
+ setTimeout(r);
142
+ });
143
+ }
138
144
  }
@@ -78,6 +78,7 @@ export class TestTree<T = string> {
78
78
  get instance() {
79
79
  if (!this.treeInstance) {
80
80
  this.treeInstance = createTree(this.config);
81
+ this.treeInstance.setMounted(true);
81
82
  this.treeInstance.rebuildTree();
82
83
  }
83
84
  return this.treeInstance;
@@ -87,10 +88,9 @@ export class TestTree<T = string> {
87
88
 
88
89
  static async resolveAsyncLoaders() {
89
90
  do {
91
+ await vi.advanceTimersToNextTimerAsync();
90
92
  TestTree.asyncLoaderResolvers.shift()?.();
91
- await new Promise<void>((r) => {
92
- setTimeout(r);
93
- });
93
+ await vi.advanceTimersToNextTimerAsync();
94
94
  } while (TestTree.asyncLoaderResolvers.length);
95
95
  }
96
96