@headless-tree/core 0.0.0-20250508233916 → 0.0.0-20250509212452

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 (39) hide show
  1. package/CHANGELOG.md +2 -1
  2. package/lib/cjs/core/create-tree.js +9 -0
  3. package/lib/cjs/features/async-data-loader/feature.js +90 -64
  4. package/lib/cjs/features/async-data-loader/types.d.ts +12 -3
  5. package/lib/cjs/features/checkboxes/feature.d.ts +2 -0
  6. package/lib/cjs/features/checkboxes/feature.js +128 -0
  7. package/lib/cjs/features/checkboxes/types.d.ts +26 -0
  8. package/lib/cjs/features/checkboxes/types.js +9 -0
  9. package/lib/cjs/features/main/types.d.ts +2 -0
  10. package/lib/cjs/features/sync-data-loader/feature.js +2 -0
  11. package/lib/cjs/features/sync-data-loader/types.d.ts +2 -2
  12. package/lib/cjs/index.d.ts +2 -0
  13. package/lib/cjs/index.js +2 -0
  14. package/lib/cjs/types/core.d.ts +2 -1
  15. package/lib/esm/core/create-tree.js +9 -0
  16. package/lib/esm/features/async-data-loader/feature.js +90 -64
  17. package/lib/esm/features/async-data-loader/types.d.ts +12 -3
  18. package/lib/esm/features/checkboxes/feature.d.ts +2 -0
  19. package/lib/esm/features/checkboxes/feature.js +125 -0
  20. package/lib/esm/features/checkboxes/types.d.ts +26 -0
  21. package/lib/esm/features/checkboxes/types.js +6 -0
  22. package/lib/esm/features/main/types.d.ts +2 -0
  23. package/lib/esm/features/sync-data-loader/feature.js +2 -0
  24. package/lib/esm/features/sync-data-loader/types.d.ts +2 -2
  25. package/lib/esm/index.d.ts +2 -0
  26. package/lib/esm/index.js +2 -0
  27. package/lib/esm/types/core.d.ts +2 -1
  28. package/package.json +1 -1
  29. package/src/core/create-tree.ts +13 -0
  30. package/src/features/async-data-loader/async-data-loader.spec.ts +36 -0
  31. package/src/features/async-data-loader/feature.ts +103 -68
  32. package/src/features/async-data-loader/types.ts +14 -3
  33. package/src/features/checkboxes/feature.ts +150 -0
  34. package/src/features/checkboxes/types.ts +28 -0
  35. package/src/features/main/types.ts +2 -0
  36. package/src/features/sync-data-loader/feature.ts +3 -0
  37. package/src/features/sync-data-loader/types.ts +2 -2
  38. package/src/index.ts +2 -0
  39. package/src/types/core.ts +2 -0
@@ -8,6 +8,58 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { makeStateUpdater } from "../../utils";
11
+ const getDataRef = (tree) => {
12
+ var _a, _b;
13
+ var _c, _d;
14
+ const dataRef = tree.getDataRef();
15
+ (_a = (_c = dataRef.current).itemData) !== null && _a !== void 0 ? _a : (_c.itemData = {});
16
+ (_b = (_d = dataRef.current).childrenIds) !== null && _b !== void 0 ? _b : (_d.childrenIds = {});
17
+ return dataRef;
18
+ };
19
+ const loadItemData = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
20
+ var _a, _b, _c;
21
+ const config = tree.getConfig();
22
+ const dataRef = getDataRef(tree);
23
+ const item = yield config.dataLoader.getItem(itemId);
24
+ dataRef.current.itemData[itemId] = item;
25
+ (_a = config.onLoadedItem) === null || _a === void 0 ? void 0 : _a.call(config, itemId, item);
26
+ tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId));
27
+ (_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[itemId].forEach((cb) => cb());
28
+ (_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[itemId];
29
+ return item;
30
+ });
31
+ const loadChildrenIds = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
32
+ var _a, _b, _c, _d, _e;
33
+ const config = tree.getConfig();
34
+ const dataRef = getDataRef(tree);
35
+ let childrenIds;
36
+ // TODO is folder check?
37
+ if ("getChildrenWithData" in config.dataLoader) {
38
+ const children = yield config.dataLoader.getChildrenWithData(itemId);
39
+ childrenIds = children.map((c) => c.id);
40
+ dataRef.current.childrenIds[itemId] = childrenIds;
41
+ children.forEach(({ id, data }) => {
42
+ var _a, _b, _c;
43
+ dataRef.current.itemData[id] = data;
44
+ (_a = config.onLoadedItem) === null || _a === void 0 ? void 0 : _a.call(config, id, data);
45
+ (_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[id].forEach((cb) => cb());
46
+ (_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[id];
47
+ });
48
+ (_a = config.onLoadedChildren) === null || _a === void 0 ? void 0 : _a.call(config, itemId, childrenIds);
49
+ tree.rebuildTree();
50
+ tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => !childrenIds.includes(id)));
51
+ }
52
+ else {
53
+ childrenIds = yield config.dataLoader.getChildren(itemId);
54
+ dataRef.current.childrenIds[itemId] = childrenIds;
55
+ (_b = config.onLoadedChildren) === null || _b === void 0 ? void 0 : _b.call(config, itemId, childrenIds);
56
+ tree.rebuildTree();
57
+ }
58
+ tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId));
59
+ (_d = (_c = dataRef.current.awaitingItemChildrensLoading) === null || _c === void 0 ? void 0 : _c[itemId]) === null || _d === void 0 ? void 0 : _d.forEach((cb) => cb());
60
+ (_e = dataRef.current.awaitingItemChildrensLoading) === null || _e === void 0 ? true : delete _e[itemId];
61
+ return childrenIds;
62
+ });
11
63
  export const asyncDataLoaderFeature = {
12
64
  key: "async-data-loader",
13
65
  getInitialState: (initialState) => (Object.assign({ loadingItemData: [], loadingItemChildrens: [] }, initialState)),
@@ -32,6 +84,7 @@ export const asyncDataLoaderFeature = {
32
84
  });
33
85
  }),
34
86
  waitForItemChildrenLoaded: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
87
+ // TODO replace inner implementation with load() fns
35
88
  tree.retrieveChildrenIds(itemId);
36
89
  if (!tree.getState().loadingItemChildrens.includes(itemId)) {
37
90
  return;
@@ -45,92 +98,65 @@ export const asyncDataLoaderFeature = {
45
98
  dataRef.current.awaitingItemChildrensLoading[itemId].push(resolve);
46
99
  });
47
100
  }),
48
- retrieveItemData: ({ tree }, itemId) => {
49
- var _a, _b, _c, _d;
50
- var _e, _f;
101
+ loadItemData: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
102
+ var _b;
103
+ return ((_b = getDataRef(tree).current.itemData[itemId]) !== null && _b !== void 0 ? _b : (yield loadItemData(tree, itemId)));
104
+ }),
105
+ loadChildrenIds: (_a, itemId_1) => __awaiter(void 0, [_a, itemId_1], void 0, function* ({ tree }, itemId) {
106
+ var _b;
107
+ return ((_b = getDataRef(tree).current.childrenIds[itemId]) !== null && _b !== void 0 ? _b : (yield loadChildrenIds(tree, itemId)));
108
+ }),
109
+ retrieveItemData: ({ tree }, itemId, skipFetch = false) => {
110
+ var _a, _b;
51
111
  const config = tree.getConfig();
52
- const dataRef = tree.getDataRef();
53
- (_a = (_e = dataRef.current).itemData) !== null && _a !== void 0 ? _a : (_e.itemData = {});
54
- (_b = (_f = dataRef.current).childrenIds) !== null && _b !== void 0 ? _b : (_f.childrenIds = {});
112
+ const dataRef = getDataRef(tree);
55
113
  if (dataRef.current.itemData[itemId]) {
56
114
  return dataRef.current.itemData[itemId];
57
115
  }
58
- if (!tree.getState().loadingItemData.includes(itemId)) {
116
+ if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) {
59
117
  tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
60
118
  ...loadingItemData,
61
119
  itemId,
62
120
  ]);
63
- (() => __awaiter(void 0, void 0, void 0, function* () {
64
- var _a, _b, _c;
65
- const item = yield config.dataLoader.getItem(itemId);
66
- dataRef.current.itemData[itemId] = item;
67
- (_a = config.onLoadedItem) === null || _a === void 0 ? void 0 : _a.call(config, itemId, item);
68
- tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId));
69
- (_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[itemId].forEach((cb) => cb());
70
- (_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[itemId];
71
- }))();
121
+ loadItemData(tree, itemId);
72
122
  }
73
- return (_d = (_c = config.createLoadingItemData) === null || _c === void 0 ? void 0 : _c.call(config)) !== null && _d !== void 0 ? _d : null;
123
+ return (_b = (_a = config.createLoadingItemData) === null || _a === void 0 ? void 0 : _a.call(config)) !== null && _b !== void 0 ? _b : null;
74
124
  },
75
- retrieveChildrenIds: ({ tree }, itemId) => {
76
- var _a, _b;
77
- var _c, _d;
78
- const config = tree.getConfig();
79
- const dataRef = tree.getDataRef();
80
- (_a = (_c = dataRef.current).itemData) !== null && _a !== void 0 ? _a : (_c.itemData = {});
81
- (_b = (_d = dataRef.current).childrenIds) !== null && _b !== void 0 ? _b : (_d.childrenIds = {});
125
+ retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => {
126
+ const dataRef = getDataRef(tree);
82
127
  if (dataRef.current.childrenIds[itemId]) {
83
128
  return dataRef.current.childrenIds[itemId];
84
129
  }
85
- if (tree.getState().loadingItemChildrens.includes(itemId)) {
130
+ if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) {
86
131
  return [];
87
132
  }
88
133
  tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [...loadingItemChildrens, itemId]);
89
- (() => __awaiter(void 0, void 0, void 0, function* () {
90
- var _a, _b, _c, _d, _e;
91
- if ("getChildrenWithData" in config.dataLoader) {
92
- const children = yield config.dataLoader.getChildrenWithData(itemId);
93
- const childrenIds = children.map((c) => c.id);
94
- dataRef.current.childrenIds[itemId] = childrenIds;
95
- children.forEach(({ id, data }) => {
96
- var _a, _b, _c;
97
- dataRef.current.itemData[id] = data;
98
- (_a = config.onLoadedItem) === null || _a === void 0 ? void 0 : _a.call(config, id, data);
99
- (_b = dataRef.current.awaitingItemDataLoading) === null || _b === void 0 ? void 0 : _b[id].forEach((cb) => cb());
100
- (_c = dataRef.current.awaitingItemDataLoading) === null || _c === void 0 ? true : delete _c[id];
101
- });
102
- (_a = config.onLoadedChildren) === null || _a === void 0 ? void 0 : _a.call(config, itemId, childrenIds);
103
- tree.rebuildTree();
104
- tree.applySubStateUpdate("loadingItemData", (loadingItemData) => loadingItemData.filter((id) => !childrenIds.includes(id)));
105
- }
106
- else {
107
- const childrenIds = yield config.dataLoader.getChildren(itemId);
108
- dataRef.current.childrenIds[itemId] = childrenIds;
109
- (_b = config.onLoadedChildren) === null || _b === void 0 ? void 0 : _b.call(config, itemId, childrenIds);
110
- tree.rebuildTree();
111
- }
112
- tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId));
113
- (_d = (_c = dataRef.current.awaitingItemChildrensLoading) === null || _c === void 0 ? void 0 : _c[itemId]) === null || _d === void 0 ? void 0 : _d.forEach((cb) => cb());
114
- (_e = dataRef.current.awaitingItemChildrensLoading) === null || _e === void 0 ? true : delete _e[itemId];
115
- }))();
134
+ loadChildrenIds(tree, itemId);
116
135
  return [];
117
136
  },
118
137
  },
119
138
  itemInstance: {
120
139
  isLoading: ({ tree, item }) => tree.getState().loadingItemData.includes(item.getItemMeta().itemId) ||
121
140
  tree.getState().loadingItemChildrens.includes(item.getItemMeta().itemId),
122
- invalidateItemData: ({ tree, itemId }) => {
123
- var _a;
124
- const dataRef = tree.getDataRef();
125
- (_a = dataRef.current.itemData) === null || _a === void 0 ? true : delete _a[itemId];
126
- tree.retrieveItemData(itemId);
127
- },
128
- invalidateChildrenIds: ({ tree, itemId }) => {
129
- var _a;
130
- const dataRef = tree.getDataRef();
131
- (_a = dataRef.current.childrenIds) === null || _a === void 0 ? true : delete _a[itemId];
132
- tree.retrieveChildrenIds(itemId);
133
- },
141
+ invalidateItemData: (_a, optimistic_1) => __awaiter(void 0, [_a, optimistic_1], void 0, function* ({ tree, itemId }, optimistic) {
142
+ var _b;
143
+ if (!optimistic) {
144
+ (_b = getDataRef(tree).current.itemData) === null || _b === void 0 ? true : delete _b[itemId];
145
+ tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [
146
+ ...loadingItemData,
147
+ itemId,
148
+ ]);
149
+ }
150
+ yield loadItemData(tree, itemId);
151
+ }),
152
+ invalidateChildrenIds: (_a, optimistic_1) => __awaiter(void 0, [_a, optimistic_1], void 0, function* ({ tree, itemId }, optimistic) {
153
+ var _b;
154
+ if (!optimistic) {
155
+ (_b = getDataRef(tree).current.childrenIds) === null || _b === void 0 ? true : delete _b[itemId];
156
+ tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [...loadingItemChildrens, itemId]);
157
+ }
158
+ yield loadChildrenIds(tree, itemId);
159
+ }),
134
160
  updateCachedChildrenIds: ({ tree, itemId }, childrenIds) => {
135
161
  const dataRef = tree.getDataRef();
136
162
  dataRef.current.childrenIds[itemId] = childrenIds;
@@ -27,13 +27,22 @@ export type AsyncDataLoaderFeatureDef<T> = {
27
27
  onLoadedChildren?: (itemId: string, childrenIds: string[]) => void;
28
28
  };
29
29
  treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & {
30
+ /** @deprecated use loadItemData instead */
30
31
  waitForItemDataLoaded: (itemId: string) => Promise<void>;
32
+ /** @deprecated use loadChildrenIds instead */
31
33
  waitForItemChildrenLoaded: (itemId: string) => Promise<void>;
34
+ loadItemData: (itemId: string) => Promise<T>;
35
+ loadChildrenIds: (itemId: string) => Promise<string[]>;
32
36
  };
33
37
  itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & {
34
- /** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible */
35
- invalidateItemData: () => void;
36
- invalidateChildrenIds: () => void;
38
+ /** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible
39
+ * @param optimistic If true, the item will not trigger a state update on `loadingItemData`, and
40
+ * the tree will continue to display the old data until the new data has loaded. */
41
+ invalidateItemData: (optimistic?: boolean) => Promise<void>;
42
+ /** Invalidate fetched children ids for item, and triggers a refetch and subsequent rerender if the item is visible
43
+ * @param optimistic If true, the item will not trigger a state update on `loadingItemChildrens`, and
44
+ * the tree will continue to display the old data until the new data has loaded. */
45
+ invalidateChildrenIds: (optimistic?: boolean) => Promise<void>;
37
46
  updateCachedChildrenIds: (childrenIds: string[]) => void;
38
47
  isLoading: () => boolean;
39
48
  };
@@ -0,0 +1,2 @@
1
+ import { FeatureImplementation } from "../../types/core";
2
+ export declare const checkboxesFeature: FeatureImplementation;
@@ -0,0 +1,125 @@
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 { makeStateUpdater } from "../../utils";
11
+ import { CheckedState } from "./types";
12
+ /*
13
+ * Cases for checking:
14
+ * - Check an unchecked item in an unchecked or indeterminate folder
15
+ * - Check an explicitly unchecked item in a checked folder
16
+ * - Check an unchecked folder in an unchecked or indeterminate folder
17
+ *
18
+ * Cases for unchecking:
19
+ * - Uncheck a checked item in an indeterminate folder
20
+ * - Uncheck an explicitly unchecked item in an checked folder
21
+ */
22
+ const fetchAllDescendants = (tree, itemId) => __awaiter(void 0, void 0, void 0, function* () {
23
+ const children = yield tree.loadChildrenIds(itemId);
24
+ return [
25
+ itemId,
26
+ ...(yield Promise.all(children.map((child) => fetchAllDescendants(tree, child)))).flat(),
27
+ ];
28
+ });
29
+ const getAllLoadedDescendants = (tree, itemId) => {
30
+ const children = tree.retrieveChildrenIds(itemId, true);
31
+ return [
32
+ itemId,
33
+ ...children.map((child) => getAllLoadedDescendants(tree, child)).flat(),
34
+ ];
35
+ };
36
+ export const checkboxesFeature = {
37
+ key: "checkboxes",
38
+ overwrites: ["selection"],
39
+ getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
40
+ getDefaultConfig: (defaultConfig, tree) => (Object.assign({ setCheckedItems: makeStateUpdater("checkedItems", tree) }, defaultConfig)),
41
+ stateHandlerNames: {
42
+ checkedItems: "setCheckedItems",
43
+ },
44
+ treeInstance: {
45
+ setCheckedItems: ({ tree }, checkedItems) => {
46
+ tree.applySubStateUpdate("checkedItems", checkedItems);
47
+ },
48
+ },
49
+ itemInstance: {
50
+ getCheckboxProps: ({ item, itemId }) => {
51
+ const checkedState = item.getCheckedState();
52
+ // console.log("prop", itemId, checkedState);
53
+ return {
54
+ onChange: item.toggleCheckedState,
55
+ checked: checkedState === CheckedState.Checked,
56
+ ref: (r) => {
57
+ if (r) {
58
+ // console.log("ref", itemId, checkedState);
59
+ r.indeterminate = checkedState === CheckedState.Indeterminate;
60
+ }
61
+ },
62
+ };
63
+ },
64
+ toggleCheckedState: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item }) {
65
+ if (item.getCheckedState() === CheckedState.Checked) {
66
+ yield item.setUnchecked();
67
+ }
68
+ else {
69
+ yield item.setChecked();
70
+ }
71
+ }),
72
+ getCheckedState: ({ item, tree, itemId }) => {
73
+ // TODO checkedcache
74
+ const { checkedItems } = tree.getState();
75
+ if (checkedItems.includes(itemId)) {
76
+ return CheckedState.Checked;
77
+ }
78
+ if (item.isFolder()) {
79
+ const descendants = getAllLoadedDescendants(tree, itemId);
80
+ console.log("descendants of ", itemId, descendants);
81
+ if (descendants.every((d) => checkedItems.includes(d))) {
82
+ return CheckedState.Checked;
83
+ }
84
+ if (descendants.some((d) => checkedItems.includes(d))) {
85
+ return CheckedState.Indeterminate;
86
+ }
87
+ }
88
+ // if (
89
+ // item.isFolder() &&
90
+ // checkedItems.some((checkedItem) =>
91
+ // tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
92
+ // )
93
+ // ) {
94
+ // // TODO for every descendent, not every checked item
95
+ // return checkedItems.every((checkedItem) =>
96
+ // tree.getItemInstance(checkedItem)?.isDescendentOf(itemId),
97
+ // )
98
+ // ? CheckedState.Checked
99
+ // : CheckedState.Indeterminate;
100
+ // }
101
+ return CheckedState.Unchecked;
102
+ },
103
+ setChecked: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item, tree, itemId }) {
104
+ if (!item.isFolder() || tree.getConfig().canCheckFolders) {
105
+ tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
106
+ }
107
+ else {
108
+ const descendants = yield fetchAllDescendants(tree, itemId);
109
+ tree.applySubStateUpdate("checkedItems", (items) => [
110
+ ...items,
111
+ ...descendants,
112
+ ]);
113
+ }
114
+ }),
115
+ setUnchecked: (_a) => __awaiter(void 0, [_a], void 0, function* ({ item, tree, itemId }) {
116
+ if (!item.isFolder() || tree.getConfig().canCheckFolders) {
117
+ tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
118
+ }
119
+ else {
120
+ yield tree.loadChildrenIds(itemId);
121
+ item.getChildren().forEach((item) => item.setUnchecked());
122
+ }
123
+ }),
124
+ },
125
+ };
@@ -0,0 +1,26 @@
1
+ import { SetStateFn } from "../../types/core";
2
+ export declare enum CheckedState {
3
+ Checked = "checked",
4
+ Unchecked = "unchecked",
5
+ Indeterminate = "indeterminate"
6
+ }
7
+ export type CheckboxesFeatureDef<T> = {
8
+ state: {
9
+ checkedItems: string[];
10
+ };
11
+ config: {
12
+ setCheckedItems?: SetStateFn<string[]>;
13
+ canCheckFolders?: boolean;
14
+ };
15
+ treeInstance: {
16
+ setCheckedItems: (checkedItems: string[]) => void;
17
+ };
18
+ itemInstance: {
19
+ setChecked: () => Promise<void>;
20
+ setUnchecked: () => Promise<void>;
21
+ toggleCheckedState: () => Promise<void>;
22
+ getCheckedState: () => CheckedState;
23
+ getCheckboxProps: () => Record<string, any>;
24
+ };
25
+ hotkeys: never;
26
+ };
@@ -0,0 +1,6 @@
1
+ export var CheckedState;
2
+ (function (CheckedState) {
3
+ CheckedState["Checked"] = "checked";
4
+ CheckedState["Unchecked"] = "unchecked";
5
+ CheckedState["Indeterminate"] = "indeterminate";
6
+ })(CheckedState || (CheckedState = {}));
@@ -17,6 +17,8 @@ export type MainFeatureDef<T = any> = {
17
17
  treeInstance: {
18
18
  /** @internal */
19
19
  applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void;
20
+ /** @internal */
21
+ buildItemInstance: (itemId: string) => ItemInstance<T>;
20
22
  setState: SetStateFn<TreeState<T>>;
21
23
  getState: () => TreeState<T>;
22
24
  setConfig: SetStateFn<TreeConfig<T>>;
@@ -37,6 +37,8 @@ export const syncDataLoaderFeature = {
37
37
  }
38
38
  return unpromise(dataLoader.getChildrenWithData(itemId)).map((c) => c.data);
39
39
  },
40
+ loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId),
41
+ loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId),
40
42
  },
41
43
  itemInstance: {
42
44
  isLoading: () => false,
@@ -18,8 +18,8 @@ export type SyncDataLoaderFeatureDef<T> = {
18
18
  dataLoader: TreeDataLoader<T>;
19
19
  };
20
20
  treeInstance: {
21
- retrieveItemData: (itemId: string) => T;
22
- retrieveChildrenIds: (itemId: string) => string[];
21
+ retrieveItemData: (itemId: string, skipFetch?: boolean) => T;
22
+ retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[];
23
23
  };
24
24
  itemInstance: {
25
25
  isLoading: () => boolean;
@@ -5,6 +5,7 @@ export { MainFeatureDef, InstanceBuilder } from "./features/main/types";
5
5
  export * from "./features/drag-and-drop/types";
6
6
  export * from "./features/keyboard-drag-and-drop/types";
7
7
  export * from "./features/selection/types";
8
+ export * from "./features/checkboxes/types";
8
9
  export * from "./features/async-data-loader/types";
9
10
  export * from "./features/sync-data-loader/types";
10
11
  export * from "./features/hotkeys-core/types";
@@ -13,6 +14,7 @@ export * from "./features/renaming/types";
13
14
  export * from "./features/expand-all/types";
14
15
  export * from "./features/prop-memoization/types";
15
16
  export * from "./features/selection/feature";
17
+ export * from "./features/checkboxes/feature";
16
18
  export * from "./features/hotkeys-core/feature";
17
19
  export * from "./features/async-data-loader/feature";
18
20
  export * from "./features/sync-data-loader/feature";
package/lib/esm/index.js CHANGED
@@ -4,6 +4,7 @@ export * from "./features/tree/types";
4
4
  export * from "./features/drag-and-drop/types";
5
5
  export * from "./features/keyboard-drag-and-drop/types";
6
6
  export * from "./features/selection/types";
7
+ export * from "./features/checkboxes/types";
7
8
  export * from "./features/async-data-loader/types";
8
9
  export * from "./features/sync-data-loader/types";
9
10
  export * from "./features/hotkeys-core/types";
@@ -12,6 +13,7 @@ export * from "./features/renaming/types";
12
13
  export * from "./features/expand-all/types";
13
14
  export * from "./features/prop-memoization/types";
14
15
  export * from "./features/selection/feature";
16
+ export * from "./features/checkboxes/feature";
15
17
  export * from "./features/hotkeys-core/feature";
16
18
  export * from "./features/async-data-loader/feature";
17
19
  export * from "./features/sync-data-loader/feature";
@@ -10,6 +10,7 @@ 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
12
  import { KeyboardDragAndDropFeatureDef } from "../features/keyboard-drag-and-drop/types";
13
+ import { CheckboxesFeatureDef } from "../features/checkboxes/types";
13
14
  export type Updater<T> = T | ((old: T) => T);
14
15
  export type SetStateFn<T> = (updaterOrValue: Updater<T>) => void;
15
16
  export type FeatureDef = {
@@ -34,7 +35,7 @@ type MergedFeatures<F extends FeatureDef> = {
34
35
  itemInstance: UnionToIntersection<F["itemInstance"]>;
35
36
  hotkeys: F["hotkeys"];
36
37
  };
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;
38
+ export type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | CheckboxesFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef;
38
39
  type TreeStateType<T> = MergedFeatures<RegisteredFeatures<T>>["state"];
39
40
  export interface TreeState<T> extends TreeStateType<T> {
40
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-tree/core",
3
- "version": "0.0.0-20250508233916",
3
+ "version": "0.0.0-20250509212452",
4
4
  "type": "module",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/esm/index.js",
@@ -151,6 +151,19 @@ export const createTree = <T>(
151
151
  ] as Function;
152
152
  externalStateSetter?.(state[stateName]);
153
153
  },
154
+ buildItemInstance: ({}, itemId) => {
155
+ const [instance, finalizeInstance] = buildInstance(
156
+ features,
157
+ "itemInstance",
158
+ (instance) => ({
159
+ item: instance,
160
+ tree: treeInstance,
161
+ itemId,
162
+ }),
163
+ );
164
+ finalizeInstance();
165
+ return instance;
166
+ },
154
167
  // TODO rebuildSubTree: (itemId: string) => void;
155
168
  rebuildTree: () => {
156
169
  rebuildItemMeta();
@@ -86,6 +86,7 @@ describe("core-feature/selections", () => {
86
86
  suiteTree.resetBeforeEach();
87
87
 
88
88
  it("invalidates item data on item instance", async () => {
89
+ const setLoadingItemData = suiteTree.mockedHandler("setLoadingItemData");
89
90
  getItem.mockClear();
90
91
  await suiteTree.resolveAsyncVisibleItems();
91
92
  getItem.mockResolvedValueOnce("new");
@@ -93,9 +94,14 @@ describe("core-feature/selections", () => {
93
94
  await suiteTree.resolveAsyncVisibleItems();
94
95
  expect(getItem).toHaveBeenCalledWith("x1");
95
96
  expect(suiteTree.item("x1").getItemData()).toBe("new");
97
+ expect(setLoadingItemData).toBeCalledWith(["x1"]);
98
+ expect(setLoadingItemData).toBeCalledWith([]);
96
99
  });
97
100
 
98
101
  it("invalidates children ids on item instance", async () => {
102
+ const setLoadingItemChildrens = suiteTree.mockedHandler(
103
+ "setLoadingItemChildrens",
104
+ );
99
105
  getChildren.mockClear();
100
106
  await suiteTree.resolveAsyncVisibleItems();
101
107
  getChildren.mockResolvedValueOnce(["new1", "new2"]);
@@ -103,6 +109,8 @@ describe("core-feature/selections", () => {
103
109
  await suiteTree.resolveAsyncVisibleItems();
104
110
  expect(getChildren).toHaveBeenCalledWith("x1");
105
111
  suiteTree.expect.hasChildren("x1", ["new1", "new2"]);
112
+ expect(setLoadingItemChildrens).toBeCalledWith(["x1"]);
113
+ expect(setLoadingItemChildrens).toBeCalledWith([]);
106
114
  });
107
115
 
108
116
  it("doesnt call item data getter twice", async () => {
@@ -124,6 +132,34 @@ describe("core-feature/selections", () => {
124
132
  suiteTree.expect.hasChildren("x1", ["x11", "x12", "x13", "x14"]);
125
133
  expect(getChildren).toHaveBeenCalledTimes(1);
126
134
  });
135
+
136
+ it("optimistic invalidates item data on item instance", async () => {
137
+ const setLoadingItemData = suiteTree.mockedHandler("setLoadingItemData");
138
+ getItem.mockClear();
139
+ await suiteTree.resolveAsyncVisibleItems();
140
+ getItem.mockResolvedValueOnce("new");
141
+ suiteTree.item("x1").invalidateItemData(true);
142
+ await suiteTree.resolveAsyncVisibleItems();
143
+ expect(getItem).toHaveBeenCalledWith("x1");
144
+ expect(suiteTree.item("x1").getItemData()).toBe("new");
145
+ expect(setLoadingItemData).toBeCalledTimes(1);
146
+ expect(setLoadingItemData).toBeCalledWith([]);
147
+ });
148
+
149
+ it("optimistic invalidates children ids on item instance", async () => {
150
+ const setLoadingItemChildrens = suiteTree.mockedHandler(
151
+ "setLoadingItemChildrens",
152
+ );
153
+ getChildren.mockClear();
154
+ await suiteTree.resolveAsyncVisibleItems();
155
+ getChildren.mockResolvedValueOnce(["new1", "new2"]);
156
+ suiteTree.item("x1").invalidateChildrenIds(true);
157
+ await suiteTree.resolveAsyncVisibleItems();
158
+ expect(getChildren).toHaveBeenCalledWith("x1");
159
+ suiteTree.expect.hasChildren("x1", ["new1", "new2"]);
160
+ expect(setLoadingItemChildrens).toBeCalledTimes(1);
161
+ expect(setLoadingItemChildrens).toBeCalledWith([]);
162
+ });
127
163
  });
128
164
 
129
165
  describe("getChildrenWithData", () => {