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

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,6 +1,6 @@
1
1
  # @headless-tree/core
2
2
 
3
- ## 0.0.0-20250723222210
3
+ ## 0.0.0-20250725212154
4
4
 
5
5
  ### Patch Changes
6
6
 
@@ -4,26 +4,30 @@ exports.checkboxesFeature = void 0;
4
4
  const utils_1 = require("../../utils");
5
5
  const types_1 = require("./types");
6
6
  const errors_1 = require("../../utilities/errors");
7
- const getAllLoadedDescendants = (tree, itemId) => {
7
+ const getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
8
8
  if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
9
9
  return [itemId];
10
10
  }
11
- return tree
11
+ const descendants = tree
12
12
  .retrieveChildrenIds(itemId)
13
- .map((child) => getAllLoadedDescendants(tree, child))
13
+ .map((child) => getAllLoadedDescendants(tree, child, includeFolders))
14
14
  .flat();
15
+ return includeFolders ? [itemId, ...descendants] : descendants;
15
16
  };
16
17
  exports.checkboxesFeature = {
17
18
  key: "checkboxes",
18
19
  overwrites: ["selection"],
19
20
  getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
20
21
  getDefaultConfig: (defaultConfig, tree) => {
21
- var _a;
22
+ var _a, _b, _c;
22
23
  const hasAsyncLoader = (_a = defaultConfig.features) === null || _a === void 0 ? void 0 : _a.some((f) => f.key === "async-data-loader");
23
- if (hasAsyncLoader && !defaultConfig.canCheckFolders) {
24
- (0, errors_1.throwError)(`!canCheckFolders not supported with async trees`);
24
+ if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
25
+ (0, errors_1.throwError)(`propagateCheckedState not supported with async trees`);
25
26
  }
26
- return Object.assign({ setCheckedItems: (0, utils_1.makeStateUpdater)("checkedItems", tree), canCheckFolders: hasAsyncLoader !== null && hasAsyncLoader !== void 0 ? hasAsyncLoader : false }, defaultConfig);
27
+ const propagateCheckedState = (_b = defaultConfig.propagateCheckedState) !== null && _b !== void 0 ? _b : !hasAsyncLoader;
28
+ const canCheckFolders = (_c = defaultConfig.canCheckFolders) !== null && _c !== void 0 ? _c : !propagateCheckedState;
29
+ return Object.assign({ setCheckedItems: (0, utils_1.makeStateUpdater)("checkedItems", tree), propagateCheckedState,
30
+ canCheckFolders }, defaultConfig);
27
31
  },
28
32
  stateHandlerNames: {
29
33
  checkedItems: "setCheckedItems",
@@ -54,12 +58,14 @@ exports.checkboxesFeature = {
54
58
  item.setChecked();
55
59
  }
56
60
  },
57
- getCheckedState: ({ item, tree, itemId }) => {
61
+ getCheckedState: ({ item, tree }) => {
58
62
  const { checkedItems } = tree.getState();
63
+ const { propagateCheckedState } = tree.getConfig();
64
+ const itemId = item.getId();
59
65
  if (checkedItems.includes(itemId)) {
60
66
  return types_1.CheckedState.Checked;
61
67
  }
62
- if (item.isFolder() && !tree.getConfig().canCheckFolders) {
68
+ if (item.isFolder() && propagateCheckedState) {
63
69
  const descendants = getAllLoadedDescendants(tree, itemId);
64
70
  if (descendants.every((d) => checkedItems.includes(d))) {
65
71
  return types_1.CheckedState.Checked;
@@ -71,23 +77,25 @@ exports.checkboxesFeature = {
71
77
  return types_1.CheckedState.Unchecked;
72
78
  },
73
79
  setChecked: ({ item, tree, itemId }) => {
74
- if (!item.isFolder() || tree.getConfig().canCheckFolders) {
75
- tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
76
- }
77
- else {
80
+ const { propagateCheckedState, canCheckFolders } = tree.getConfig();
81
+ if (item.isFolder() && propagateCheckedState) {
78
82
  tree.applySubStateUpdate("checkedItems", (items) => [
79
83
  ...items,
80
- ...getAllLoadedDescendants(tree, itemId),
84
+ ...getAllLoadedDescendants(tree, itemId, canCheckFolders),
81
85
  ]);
82
86
  }
87
+ else if (!item.isFolder() || canCheckFolders) {
88
+ tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
89
+ }
83
90
  },
84
91
  setUnchecked: ({ item, tree, itemId }) => {
85
- if (!item.isFolder() || tree.getConfig().canCheckFolders) {
86
- tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
92
+ const { propagateCheckedState, canCheckFolders } = tree.getConfig();
93
+ if (item.isFolder() && propagateCheckedState) {
94
+ const descendants = getAllLoadedDescendants(tree, itemId, canCheckFolders);
95
+ tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id) && id !== itemId));
87
96
  }
88
97
  else {
89
- const descendants = getAllLoadedDescendants(tree, itemId);
90
- tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id)));
98
+ tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
91
99
  }
92
100
  },
93
101
  },
@@ -11,6 +11,7 @@ export type CheckboxesFeatureDef<T> = {
11
11
  config: {
12
12
  setCheckedItems?: SetStateFn<string[]>;
13
13
  canCheckFolders?: boolean;
14
+ propagateCheckedState?: boolean;
14
15
  };
15
16
  treeInstance: {
16
17
  setCheckedItems: (checkedItems: string[]) => void;
@@ -1,26 +1,30 @@
1
1
  import { makeStateUpdater } from "../../utils";
2
2
  import { CheckedState } from "./types";
3
3
  import { throwError } from "../../utilities/errors";
4
- const getAllLoadedDescendants = (tree, itemId) => {
4
+ const getAllLoadedDescendants = (tree, itemId, includeFolders = false) => {
5
5
  if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
6
6
  return [itemId];
7
7
  }
8
- return tree
8
+ const descendants = tree
9
9
  .retrieveChildrenIds(itemId)
10
- .map((child) => getAllLoadedDescendants(tree, child))
10
+ .map((child) => getAllLoadedDescendants(tree, child, includeFolders))
11
11
  .flat();
12
+ return includeFolders ? [itemId, ...descendants] : descendants;
12
13
  };
13
14
  export const checkboxesFeature = {
14
15
  key: "checkboxes",
15
16
  overwrites: ["selection"],
16
17
  getInitialState: (initialState) => (Object.assign({ checkedItems: [] }, initialState)),
17
18
  getDefaultConfig: (defaultConfig, tree) => {
18
- var _a;
19
+ var _a, _b, _c;
19
20
  const hasAsyncLoader = (_a = defaultConfig.features) === null || _a === void 0 ? void 0 : _a.some((f) => f.key === "async-data-loader");
20
- if (hasAsyncLoader && !defaultConfig.canCheckFolders) {
21
- throwError(`!canCheckFolders not supported with async trees`);
21
+ if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
22
+ throwError(`propagateCheckedState not supported with async trees`);
22
23
  }
23
- return Object.assign({ setCheckedItems: makeStateUpdater("checkedItems", tree), canCheckFolders: hasAsyncLoader !== null && hasAsyncLoader !== void 0 ? hasAsyncLoader : false }, defaultConfig);
24
+ const propagateCheckedState = (_b = defaultConfig.propagateCheckedState) !== null && _b !== void 0 ? _b : !hasAsyncLoader;
25
+ const canCheckFolders = (_c = defaultConfig.canCheckFolders) !== null && _c !== void 0 ? _c : !propagateCheckedState;
26
+ return Object.assign({ setCheckedItems: makeStateUpdater("checkedItems", tree), propagateCheckedState,
27
+ canCheckFolders }, defaultConfig);
24
28
  },
25
29
  stateHandlerNames: {
26
30
  checkedItems: "setCheckedItems",
@@ -51,12 +55,14 @@ export const checkboxesFeature = {
51
55
  item.setChecked();
52
56
  }
53
57
  },
54
- getCheckedState: ({ item, tree, itemId }) => {
58
+ getCheckedState: ({ item, tree }) => {
55
59
  const { checkedItems } = tree.getState();
60
+ const { propagateCheckedState } = tree.getConfig();
61
+ const itemId = item.getId();
56
62
  if (checkedItems.includes(itemId)) {
57
63
  return CheckedState.Checked;
58
64
  }
59
- if (item.isFolder() && !tree.getConfig().canCheckFolders) {
65
+ if (item.isFolder() && propagateCheckedState) {
60
66
  const descendants = getAllLoadedDescendants(tree, itemId);
61
67
  if (descendants.every((d) => checkedItems.includes(d))) {
62
68
  return CheckedState.Checked;
@@ -68,23 +74,25 @@ export const checkboxesFeature = {
68
74
  return CheckedState.Unchecked;
69
75
  },
70
76
  setChecked: ({ item, tree, itemId }) => {
71
- if (!item.isFolder() || tree.getConfig().canCheckFolders) {
72
- tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
73
- }
74
- else {
77
+ const { propagateCheckedState, canCheckFolders } = tree.getConfig();
78
+ if (item.isFolder() && propagateCheckedState) {
75
79
  tree.applySubStateUpdate("checkedItems", (items) => [
76
80
  ...items,
77
- ...getAllLoadedDescendants(tree, itemId),
81
+ ...getAllLoadedDescendants(tree, itemId, canCheckFolders),
78
82
  ]);
79
83
  }
84
+ else if (!item.isFolder() || canCheckFolders) {
85
+ tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
86
+ }
80
87
  },
81
88
  setUnchecked: ({ item, tree, itemId }) => {
82
- if (!item.isFolder() || tree.getConfig().canCheckFolders) {
83
- tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
89
+ const { propagateCheckedState, canCheckFolders } = tree.getConfig();
90
+ if (item.isFolder() && propagateCheckedState) {
91
+ const descendants = getAllLoadedDescendants(tree, itemId, canCheckFolders);
92
+ tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id) && id !== itemId));
84
93
  }
85
94
  else {
86
- const descendants = getAllLoadedDescendants(tree, itemId);
87
- tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => !descendants.includes(id)));
95
+ tree.applySubStateUpdate("checkedItems", (items) => items.filter((id) => id !== itemId));
88
96
  }
89
97
  },
90
98
  },
@@ -11,6 +11,7 @@ export type CheckboxesFeatureDef<T> = {
11
11
  config: {
12
12
  setCheckedItems?: SetStateFn<string[]>;
13
13
  canCheckFolders?: boolean;
14
+ propagateCheckedState?: boolean;
14
15
  };
15
16
  treeInstance: {
16
17
  setCheckedItems: (checkedItems: string[]) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-tree/core",
3
- "version": "0.0.0-20250723222210",
3
+ "version": "0.0.0-20250725212154",
4
4
  "main": "lib/cjs/index.js",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/esm/index.d.ts",
@@ -74,17 +74,28 @@ describe("core-feature/checkboxes", () => {
74
74
  });
75
75
  });
76
76
 
77
- it("should handle folder checking when canCheckFolders is true", async () => {
77
+ it("should handle folder checking", async () => {
78
78
  const tree = await factory
79
- .with({ canCheckFolders: true })
79
+ .with({ canCheckFolders: true, propagateCheckedState: false })
80
80
  .createTestCaseTree();
81
81
 
82
82
  tree.item("x11").setChecked();
83
83
  expect(tree.instance.getState().checkedItems).toContain("x11");
84
84
  });
85
85
 
86
- it("should handle folder checking when canCheckFolders is false", async () => {
87
- const tree = await factory.createTestCaseTree();
86
+ it("should not check folders if disabled", async () => {
87
+ const tree = await factory
88
+ .with({ canCheckFolders: false, propagateCheckedState: false })
89
+ .createTestCaseTree();
90
+
91
+ tree.item("x11").setChecked();
92
+ expect(tree.instance.getState().checkedItems.length).toBe(0);
93
+ });
94
+
95
+ it("should propagate checked state", async () => {
96
+ const tree = await factory
97
+ .with({ propagateCheckedState: true })
98
+ .createTestCaseTree();
88
99
 
89
100
  tree.item("x11").setChecked();
90
101
  expect(tree.instance.getState().checkedItems).toEqual(
@@ -93,7 +104,9 @@ describe("core-feature/checkboxes", () => {
93
104
  });
94
105
 
95
106
  it("should turn folder indeterminate", async () => {
96
- const tree = await factory.createTestCaseTree();
107
+ const tree = await factory
108
+ .with({ propagateCheckedState: true })
109
+ .createTestCaseTree();
97
110
 
98
111
  tree.item("x111").setChecked();
99
112
  expect(tree.item("x11").getCheckedState()).toBe(CheckedState.Indeterminate);
@@ -103,6 +116,8 @@ describe("core-feature/checkboxes", () => {
103
116
  const tree = await factory
104
117
  .with({
105
118
  isItemFolder: (item) => item.getItemData().length < 4,
119
+ propagateCheckedState: true,
120
+ canCheckFolders: false,
106
121
  })
107
122
  .createTestCaseTree();
108
123
 
@@ -6,14 +6,16 @@ import { throwError } from "../../utilities/errors";
6
6
  const getAllLoadedDescendants = <T>(
7
7
  tree: TreeInstance<T>,
8
8
  itemId: string,
9
+ includeFolders = false,
9
10
  ): string[] => {
10
11
  if (!tree.getConfig().isItemFolder(tree.buildItemInstance(itemId))) {
11
12
  return [itemId];
12
13
  }
13
- return tree
14
+ const descendants = tree
14
15
  .retrieveChildrenIds(itemId)
15
- .map((child) => getAllLoadedDescendants(tree, child))
16
+ .map((child) => getAllLoadedDescendants(tree, child, includeFolders))
16
17
  .flat();
18
+ return includeFolders ? [itemId, ...descendants] : descendants;
17
19
  };
18
20
 
19
21
  export const checkboxesFeature: FeatureImplementation = {
@@ -30,12 +32,17 @@ export const checkboxesFeature: FeatureImplementation = {
30
32
  const hasAsyncLoader = defaultConfig.features?.some(
31
33
  (f) => f.key === "async-data-loader",
32
34
  );
33
- if (hasAsyncLoader && !defaultConfig.canCheckFolders) {
34
- throwError(`!canCheckFolders not supported with async trees`);
35
+ if (hasAsyncLoader && defaultConfig.propagateCheckedState) {
36
+ throwError(`propagateCheckedState not supported with async trees`);
35
37
  }
38
+ const propagateCheckedState =
39
+ defaultConfig.propagateCheckedState ?? !hasAsyncLoader;
40
+ const canCheckFolders =
41
+ defaultConfig.canCheckFolders ?? !propagateCheckedState;
36
42
  return {
37
43
  setCheckedItems: makeStateUpdater("checkedItems", tree),
38
- canCheckFolders: hasAsyncLoader ?? false,
44
+ propagateCheckedState,
45
+ canCheckFolders,
39
46
  ...defaultConfig,
40
47
  };
41
48
  },
@@ -72,14 +79,16 @@ export const checkboxesFeature: FeatureImplementation = {
72
79
  }
73
80
  },
74
81
 
75
- getCheckedState: ({ item, tree, itemId }) => {
82
+ getCheckedState: ({ item, tree }) => {
76
83
  const { checkedItems } = tree.getState();
84
+ const { propagateCheckedState } = tree.getConfig();
85
+ const itemId = item.getId();
77
86
 
78
87
  if (checkedItems.includes(itemId)) {
79
88
  return CheckedState.Checked;
80
89
  }
81
90
 
82
- if (item.isFolder() && !tree.getConfig().canCheckFolders) {
91
+ if (item.isFolder() && propagateCheckedState) {
83
92
  const descendants = getAllLoadedDescendants(tree, itemId);
84
93
  if (descendants.every((d) => checkedItems.includes(d))) {
85
94
  return CheckedState.Checked;
@@ -93,25 +102,31 @@ export const checkboxesFeature: FeatureImplementation = {
93
102
  },
94
103
 
95
104
  setChecked: ({ item, tree, itemId }) => {
96
- if (!item.isFolder() || tree.getConfig().canCheckFolders) {
97
- tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
98
- } else {
105
+ const { propagateCheckedState, canCheckFolders } = tree.getConfig();
106
+ if (item.isFolder() && propagateCheckedState) {
99
107
  tree.applySubStateUpdate("checkedItems", (items) => [
100
108
  ...items,
101
- ...getAllLoadedDescendants(tree, itemId),
109
+ ...getAllLoadedDescendants(tree, itemId, canCheckFolders),
102
110
  ]);
111
+ } else if (!item.isFolder() || canCheckFolders) {
112
+ tree.applySubStateUpdate("checkedItems", (items) => [...items, itemId]);
103
113
  }
104
114
  },
105
115
 
106
116
  setUnchecked: ({ item, tree, itemId }) => {
107
- if (!item.isFolder() || tree.getConfig().canCheckFolders) {
117
+ const { propagateCheckedState, canCheckFolders } = tree.getConfig();
118
+ if (item.isFolder() && propagateCheckedState) {
119
+ const descendants = getAllLoadedDescendants(
120
+ tree,
121
+ itemId,
122
+ canCheckFolders,
123
+ );
108
124
  tree.applySubStateUpdate("checkedItems", (items) =>
109
- items.filter((id) => id !== itemId),
125
+ items.filter((id) => !descendants.includes(id) && id !== itemId),
110
126
  );
111
127
  } else {
112
- const descendants = getAllLoadedDescendants(tree, itemId);
113
128
  tree.applySubStateUpdate("checkedItems", (items) =>
114
- items.filter((id) => !descendants.includes(id)),
129
+ items.filter((id) => id !== itemId),
115
130
  );
116
131
  }
117
132
  },
@@ -13,6 +13,7 @@ export type CheckboxesFeatureDef<T> = {
13
13
  config: {
14
14
  setCheckedItems?: SetStateFn<string[]>;
15
15
  canCheckFolders?: boolean;
16
+ propagateCheckedState?: boolean;
16
17
  };
17
18
  treeInstance: {
18
19
  setCheckedItems: (checkedItems: string[]) => void;