react-native-tree-multi-select 0.5.0
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/LICENSE +20 -0
- package/README.md +124 -0
- package/android/build.gradle +94 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/treemultiselect/TreeMultiSelectModule.kt +25 -0
- package/android/src/main/java/com/treemultiselect/TreeMultiSelectPackage.kt +17 -0
- package/ios/TreeMultiSelect-Bridging-Header.h +2 -0
- package/ios/TreeMultiSelect.mm +14 -0
- package/ios/TreeMultiSelect.swift +8 -0
- package/ios/TreeMultiSelect.xcodeproj/project.pbxproj +283 -0
- package/ios/TreeMultiSelect.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
- package/ios/TreeMultiSelect.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/TreeMultiSelect.xcodeproj/project.xcworkspace/xcuserdata/guest_jj.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/TreeMultiSelect.xcodeproj/xcuserdata/guest_jj.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/lib/commonjs/TreeView.js +99 -0
- package/lib/commonjs/TreeView.js.map +1 -0
- package/lib/commonjs/components/CheckboxView.js +77 -0
- package/lib/commonjs/components/CheckboxView.js.map +1 -0
- package/lib/commonjs/components/CustomExpandCollapseIcon.js +20 -0
- package/lib/commonjs/components/CustomExpandCollapseIcon.js.map +1 -0
- package/lib/commonjs/components/NodeList.js +193 -0
- package/lib/commonjs/components/NodeList.js.map +1 -0
- package/lib/commonjs/helpers/expandCollapse.helper.js +93 -0
- package/lib/commonjs/helpers/expandCollapse.helper.js.map +1 -0
- package/lib/commonjs/helpers/index.js +61 -0
- package/lib/commonjs/helpers/index.js.map +1 -0
- package/lib/commonjs/helpers/initNodeMap.helper.js +44 -0
- package/lib/commonjs/helpers/initNodeMap.helper.js.map +1 -0
- package/lib/commonjs/helpers/search.helper.js +29 -0
- package/lib/commonjs/helpers/search.helper.js.map +1 -0
- package/lib/commonjs/helpers/selectAll.helper.js +73 -0
- package/lib/commonjs/helpers/selectAll.helper.js.map +1 -0
- package/lib/commonjs/helpers/toggleCheckbox.helper.js +153 -0
- package/lib/commonjs/helpers/toggleCheckbox.helper.js.map +1 -0
- package/lib/commonjs/index.js +28 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/signals/global.signals.js +42 -0
- package/lib/commonjs/signals/global.signals.js.map +1 -0
- package/lib/commonjs/types/treeView.types.js +6 -0
- package/lib/commonjs/types/treeView.types.js.map +1 -0
- package/lib/module/TreeView.js +89 -0
- package/lib/module/TreeView.js.map +1 -0
- package/lib/module/components/CheckboxView.js +68 -0
- package/lib/module/components/CheckboxView.js.map +1 -0
- package/lib/module/components/CustomExpandCollapseIcon.js +13 -0
- package/lib/module/components/CustomExpandCollapseIcon.js.map +1 -0
- package/lib/module/components/NodeList.js +185 -0
- package/lib/module/components/NodeList.js.map +1 -0
- package/lib/module/helpers/expandCollapse.helper.js +86 -0
- package/lib/module/helpers/expandCollapse.helper.js.map +1 -0
- package/lib/module/helpers/index.js +6 -0
- package/lib/module/helpers/index.js.map +1 -0
- package/lib/module/helpers/initNodeMap.helper.js +39 -0
- package/lib/module/helpers/initNodeMap.helper.js.map +1 -0
- package/lib/module/helpers/search.helper.js +23 -0
- package/lib/module/helpers/search.helper.js.map +1 -0
- package/lib/module/helpers/selectAll.helper.js +65 -0
- package/lib/module/helpers/selectAll.helper.js.map +1 -0
- package/lib/module/helpers/toggleCheckbox.helper.js +148 -0
- package/lib/module/helpers/toggleCheckbox.helper.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/signals/global.signals.js +26 -0
- package/lib/module/signals/global.signals.js.map +1 -0
- package/lib/module/types/treeView.types.js +2 -0
- package/lib/module/types/treeView.types.js.map +1 -0
- package/lib/typescript/TreeView.d.ts +4 -0
- package/lib/typescript/TreeView.d.ts.map +1 -0
- package/lib/typescript/components/CheckboxView.d.ts +24 -0
- package/lib/typescript/components/CheckboxView.d.ts.map +1 -0
- package/lib/typescript/components/CustomExpandCollapseIcon.d.ts +4 -0
- package/lib/typescript/components/CustomExpandCollapseIcon.d.ts.map +1 -0
- package/lib/typescript/components/NodeList.d.ts +14 -0
- package/lib/typescript/components/NodeList.d.ts.map +1 -0
- package/lib/typescript/helpers/expandCollapse.helper.d.ts +18 -0
- package/lib/typescript/helpers/expandCollapse.helper.d.ts.map +1 -0
- package/lib/typescript/helpers/index.d.ts +6 -0
- package/lib/typescript/helpers/index.d.ts.map +1 -0
- package/lib/typescript/helpers/initNodeMap.helper.d.ts +12 -0
- package/lib/typescript/helpers/initNodeMap.helper.d.ts.map +1 -0
- package/lib/typescript/helpers/search.helper.d.ts +14 -0
- package/lib/typescript/helpers/search.helper.d.ts.map +1 -0
- package/lib/typescript/helpers/selectAll.helper.d.ts +25 -0
- package/lib/typescript/helpers/selectAll.helper.d.ts.map +1 -0
- package/lib/typescript/helpers/toggleCheckbox.helper.d.ts +9 -0
- package/lib/typescript/helpers/toggleCheckbox.helper.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/signals/global.signals.d.ts +11 -0
- package/lib/typescript/signals/global.signals.d.ts.map +1 -0
- package/lib/typescript/types/treeView.types.d.ts +61 -0
- package/lib/typescript/types/treeView.types.d.ts.map +1 -0
- package/package.json +165 -0
- package/react-native-tree-multi-select.podspec +41 -0
- package/src/TreeView.tsx +139 -0
- package/src/components/CheckboxView.tsx +109 -0
- package/src/components/CustomExpandCollapseIcon.tsx +20 -0
- package/src/components/NodeList.tsx +278 -0
- package/src/helpers/expandCollapse.helper.ts +88 -0
- package/src/helpers/index.ts +5 -0
- package/src/helpers/initNodeMap.helper.ts +46 -0
- package/src/helpers/search.helper.ts +28 -0
- package/src/helpers/selectAll.helper.ts +59 -0
- package/src/helpers/toggleCheckbox.helper.ts +144 -0
- package/src/index.tsx +22 -0
- package/src/signals/global.signals.ts +36 -0
- package/src/types/treeView.types.ts +86 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
View
|
|
8
|
+
} from "react-native";
|
|
9
|
+
|
|
10
|
+
import { Checkbox } from 'react-native-paper';
|
|
11
|
+
import type {
|
|
12
|
+
CheckBoxViewProps,
|
|
13
|
+
CheckboxValueType
|
|
14
|
+
} from "../types/treeView.types";
|
|
15
|
+
|
|
16
|
+
function arePropsEqual(
|
|
17
|
+
prevProps: CheckBoxViewProps,
|
|
18
|
+
nextProps: CheckBoxViewProps
|
|
19
|
+
) {
|
|
20
|
+
return (
|
|
21
|
+
prevProps.value === nextProps.value &&
|
|
22
|
+
prevProps.text === nextProps.text
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const CheckboxView = React.memo(_CheckboxView, arePropsEqual);
|
|
27
|
+
|
|
28
|
+
function _CheckboxView(props: CheckBoxViewProps) {
|
|
29
|
+
const {
|
|
30
|
+
value,
|
|
31
|
+
onValueChange,
|
|
32
|
+
text,
|
|
33
|
+
|
|
34
|
+
outermostParentViewStyle = defaultCheckboxViewStyles.mainView,
|
|
35
|
+
checkboxParentViewStyle = defaultCheckboxViewStyles.checkboxView,
|
|
36
|
+
textTouchableStyle,
|
|
37
|
+
|
|
38
|
+
checkboxProps,
|
|
39
|
+
textProps = {
|
|
40
|
+
style: defaultCheckboxViewStyles.checkboxTextStyle,
|
|
41
|
+
numberOfLines: 1,
|
|
42
|
+
ellipsizeMode: "middle",
|
|
43
|
+
},
|
|
44
|
+
} = props;
|
|
45
|
+
|
|
46
|
+
function customCheckboxValueTypeToRNPaperType(
|
|
47
|
+
customCheckboxValueType: CheckboxValueType
|
|
48
|
+
) {
|
|
49
|
+
return customCheckboxValueType === 'indeterminate'
|
|
50
|
+
? 'indeterminate'
|
|
51
|
+
: customCheckboxValueType
|
|
52
|
+
? 'checked'
|
|
53
|
+
: 'unchecked';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* This function modifies the change in value when the previous state was indeterminate.
|
|
58
|
+
* If the prior value is 'indeterminate', it will mark the CheckBox as checked upon click.
|
|
59
|
+
*
|
|
60
|
+
* @param newValue This represents the updated CheckBox value after it's clicked.
|
|
61
|
+
*/
|
|
62
|
+
function onValueChangeModifier() {
|
|
63
|
+
// If the previous state was 'indeterminate', set checked to true
|
|
64
|
+
if (value === 'indeterminate') onValueChange(true);
|
|
65
|
+
else onValueChange(!value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<View
|
|
70
|
+
style={outermostParentViewStyle}>
|
|
71
|
+
<View
|
|
72
|
+
style={checkboxParentViewStyle}>
|
|
73
|
+
<Checkbox.Android
|
|
74
|
+
{...checkboxProps}
|
|
75
|
+
status={customCheckboxValueTypeToRNPaperType(value)}
|
|
76
|
+
onPress={onValueChangeModifier} />
|
|
77
|
+
</View>
|
|
78
|
+
|
|
79
|
+
{text ? (
|
|
80
|
+
<TouchableOpacity
|
|
81
|
+
style={textTouchableStyle}
|
|
82
|
+
onPress={onValueChangeModifier}>
|
|
83
|
+
<Text
|
|
84
|
+
{...textProps}>
|
|
85
|
+
{text}
|
|
86
|
+
</Text>
|
|
87
|
+
</TouchableOpacity>
|
|
88
|
+
) : null}
|
|
89
|
+
</View>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const defaultCheckboxViewStyles = StyleSheet.create({
|
|
94
|
+
mainView: {
|
|
95
|
+
alignSelf: 'center',
|
|
96
|
+
alignItems: 'center',
|
|
97
|
+
flexDirection: 'row',
|
|
98
|
+
|
|
99
|
+
marginEnd: 10
|
|
100
|
+
},
|
|
101
|
+
checkboxView: {
|
|
102
|
+
marginStart: 5,
|
|
103
|
+
transform: [{ scale: 1.2 }]
|
|
104
|
+
},
|
|
105
|
+
checkboxTextStyle: {
|
|
106
|
+
color: "black",
|
|
107
|
+
marginTop: Platform.OS === 'android' ? 2 : undefined,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import Icon from 'react-native-vector-icons/FontAwesome';
|
|
3
|
+
|
|
4
|
+
import { ExpandIconProps } from "src/types/treeView.types";
|
|
5
|
+
|
|
6
|
+
export default function CustomExpandCollapseIcon(props: ExpandIconProps) {
|
|
7
|
+
const { isExpanded } = props;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Icon
|
|
11
|
+
name={
|
|
12
|
+
isExpanded
|
|
13
|
+
? 'caret-down'
|
|
14
|
+
: 'caret-right'
|
|
15
|
+
}
|
|
16
|
+
size={20}
|
|
17
|
+
color="black"
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
type TouchableOpacityProps,
|
|
8
|
+
} from "react-native";
|
|
9
|
+
import {
|
|
10
|
+
computed,
|
|
11
|
+
effect,
|
|
12
|
+
Signal,
|
|
13
|
+
useComputed,
|
|
14
|
+
useSignal
|
|
15
|
+
} from "@preact/signals-react";
|
|
16
|
+
import { FlashList } from "@shopify/flash-list";
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
TreeFlatListProps,
|
|
20
|
+
TreeNode,
|
|
21
|
+
|
|
22
|
+
CheckboxValueType,
|
|
23
|
+
ExpandIconProps,
|
|
24
|
+
CheckBoxViewStyleProps,
|
|
25
|
+
CheckBoxViewProps,
|
|
26
|
+
__FlattenedTreeNode__,
|
|
27
|
+
} from "../types/treeView.types";
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
expanded,
|
|
31
|
+
globalData,
|
|
32
|
+
innerMostChildrenIds,
|
|
33
|
+
searchKeys,
|
|
34
|
+
searchText,
|
|
35
|
+
state
|
|
36
|
+
} from "../signals/global.signals";
|
|
37
|
+
import {
|
|
38
|
+
doesNodeContainSearchTerm,
|
|
39
|
+
handleToggleExpand,
|
|
40
|
+
toggleCheckboxes
|
|
41
|
+
} from "../helpers";
|
|
42
|
+
import { CheckboxView } from "./CheckboxView";
|
|
43
|
+
import CustomExpandCollapseIcon from "./CustomExpandCollapseIcon";
|
|
44
|
+
|
|
45
|
+
interface NodeListProps {
|
|
46
|
+
treeFlashListProps?: TreeFlatListProps;
|
|
47
|
+
checkBoxViewStyleProps?: CheckBoxViewStyleProps;
|
|
48
|
+
|
|
49
|
+
CheckboxComponent?: React.ComponentType<CheckBoxViewProps>;
|
|
50
|
+
ExpandCollapseIconComponent?: React.ComponentType<ExpandIconProps>;
|
|
51
|
+
ExpandCollapseTouchableComponent?: React.ComponentType<TouchableOpacityProps>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const NodeList = React.memo(_NodeList);
|
|
55
|
+
export default NodeList;
|
|
56
|
+
|
|
57
|
+
function _NodeList(props: NodeListProps) {
|
|
58
|
+
const {
|
|
59
|
+
treeFlashListProps,
|
|
60
|
+
checkBoxViewStyleProps,
|
|
61
|
+
|
|
62
|
+
CheckboxComponent,
|
|
63
|
+
ExpandCollapseIconComponent,
|
|
64
|
+
ExpandCollapseTouchableComponent,
|
|
65
|
+
} = props;
|
|
66
|
+
|
|
67
|
+
const filteredTree = React.useMemo(() => {
|
|
68
|
+
return computed(() => {
|
|
69
|
+
const searchTrimmed = searchText.value.trim().toLowerCase();
|
|
70
|
+
|
|
71
|
+
const filterTreeData = (_nodes: TreeNode[]): TreeNode[] => {
|
|
72
|
+
let filtered: TreeNode[] = [];
|
|
73
|
+
|
|
74
|
+
for (let node of _nodes) {
|
|
75
|
+
if (!searchTrimmed || doesNodeContainSearchTerm(node, searchTrimmed, searchKeys.value)) {
|
|
76
|
+
// If node itself matches, include it and all its descendants
|
|
77
|
+
filtered.push(node);
|
|
78
|
+
} else if (node.children) {
|
|
79
|
+
// If node does not match, check its children and include them if they match
|
|
80
|
+
const childMatches = filterTreeData(node.children);
|
|
81
|
+
if (childMatches.length > 0) {
|
|
82
|
+
// If any children match, include the node, replacing its children with the matching ones
|
|
83
|
+
filtered.push({ ...node, children: childMatches });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return filtered;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return filterTreeData(globalData.value);
|
|
92
|
+
});
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
const flattenedFilteredNodes = React.useMemo(() => {
|
|
96
|
+
return computed(() => {
|
|
97
|
+
const flattenTreeData = (
|
|
98
|
+
_nodes: TreeNode[],
|
|
99
|
+
level: number = 0,
|
|
100
|
+
): __FlattenedTreeNode__[] => {
|
|
101
|
+
let flattened: __FlattenedTreeNode__[] = [];
|
|
102
|
+
for (let node of _nodes) {
|
|
103
|
+
flattened.push({ ...node, level });
|
|
104
|
+
if (node.children && expanded.value.has(node.id)) {
|
|
105
|
+
flattened = [...flattened, ...flattenTreeData(node.children, level + 1)];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return flattened;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return flattenTreeData(filteredTree.value);
|
|
112
|
+
});
|
|
113
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
React.useEffect(() => {
|
|
117
|
+
effect(() => {
|
|
118
|
+
const allLeafIds: string[] = [];
|
|
119
|
+
const getLeafNodes = (_nodes: TreeNode[]) => {
|
|
120
|
+
for (let node of _nodes) {
|
|
121
|
+
if (node.children) {
|
|
122
|
+
getLeafNodes(node.children);
|
|
123
|
+
} else {
|
|
124
|
+
allLeafIds.push(node.id);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
getLeafNodes(filteredTree.value);
|
|
130
|
+
|
|
131
|
+
innerMostChildrenIds.value = allLeafIds;
|
|
132
|
+
});
|
|
133
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
const nodeRenderer = React.useCallback((
|
|
137
|
+
{ item }: { item: __FlattenedTreeNode__; }
|
|
138
|
+
) => {
|
|
139
|
+
return (
|
|
140
|
+
<Node
|
|
141
|
+
node={item}
|
|
142
|
+
level={item.level || 0}
|
|
143
|
+
checkBoxViewStyleProps={checkBoxViewStyleProps}
|
|
144
|
+
|
|
145
|
+
CheckboxComponent={CheckboxComponent}
|
|
146
|
+
ExpandCollapseIconComponent={ExpandCollapseIconComponent}
|
|
147
|
+
ExpandCollapseTouchableComponent={ExpandCollapseTouchableComponent}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}, [
|
|
151
|
+
CheckboxComponent,
|
|
152
|
+
ExpandCollapseIconComponent,
|
|
153
|
+
ExpandCollapseTouchableComponent,
|
|
154
|
+
checkBoxViewStyleProps
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
const keyExtractor = React.useCallback((item: TreeNode) => item.id, []);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<FlashList
|
|
161
|
+
estimatedItemSize={36}
|
|
162
|
+
removeClippedSubviews={true}
|
|
163
|
+
keyboardShouldPersistTaps="handled"
|
|
164
|
+
drawDistance={50}
|
|
165
|
+
data={flattenedFilteredNodes.value}
|
|
166
|
+
renderItem={nodeRenderer}
|
|
167
|
+
keyExtractor={keyExtractor}
|
|
168
|
+
ListHeaderComponent={<HeaderFooterView />}
|
|
169
|
+
ListFooterComponent={<HeaderFooterView />}
|
|
170
|
+
{...treeFlashListProps}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
function HeaderFooterView() {
|
|
176
|
+
return (
|
|
177
|
+
<View style={styles.defaultHeaderFooter} />
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
interface NodeProps {
|
|
182
|
+
node: __FlattenedTreeNode__;
|
|
183
|
+
level: number;
|
|
184
|
+
|
|
185
|
+
checkBoxViewStyleProps?: CheckBoxViewStyleProps;
|
|
186
|
+
|
|
187
|
+
ExpandCollapseIconComponent?: React.ComponentType<ExpandIconProps>;
|
|
188
|
+
CheckboxComponent?: React.ComponentType<CheckBoxViewProps>;
|
|
189
|
+
ExpandCollapseTouchableComponent?: React.ComponentType<TouchableOpacityProps>;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const Node = React.memo(_Node);
|
|
193
|
+
function _Node(props: NodeProps) {
|
|
194
|
+
const {
|
|
195
|
+
node: _node,
|
|
196
|
+
level,
|
|
197
|
+
|
|
198
|
+
checkBoxViewStyleProps,
|
|
199
|
+
|
|
200
|
+
ExpandCollapseIconComponent = CustomExpandCollapseIcon,
|
|
201
|
+
CheckboxComponent = CheckboxView,
|
|
202
|
+
ExpandCollapseTouchableComponent = TouchableOpacity,
|
|
203
|
+
} = props;
|
|
204
|
+
|
|
205
|
+
const node = useSignal(_node);
|
|
206
|
+
React.useEffect(() => {
|
|
207
|
+
node.value = _node;
|
|
208
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
209
|
+
}, [_node]);
|
|
210
|
+
|
|
211
|
+
const isChecked = useComputed(() => state.value.checked.has(node.value.id));
|
|
212
|
+
const isIndeterminate = useComputed(() => state.value.indeterminate.has(
|
|
213
|
+
node.value.id
|
|
214
|
+
));
|
|
215
|
+
const value: Signal<CheckboxValueType> = useComputed(() => {
|
|
216
|
+
if (isIndeterminate.value) {
|
|
217
|
+
return 'indeterminate';
|
|
218
|
+
} else if (isChecked.value) {
|
|
219
|
+
return true;
|
|
220
|
+
} else {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
const isExpanded = useComputed(() => expanded.value.has(node.value.id));
|
|
225
|
+
|
|
226
|
+
const _onToggleExpand = React.useCallback(() => {
|
|
227
|
+
handleToggleExpand(node.value.id);
|
|
228
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
229
|
+
}, []);
|
|
230
|
+
|
|
231
|
+
const _onCheck = React.useCallback(() => {
|
|
232
|
+
toggleCheckboxes([node.value.id]);
|
|
233
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
234
|
+
}, []);
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<View style={[
|
|
238
|
+
styles.nodeParentView,
|
|
239
|
+
{ marginLeft: level * 15, }
|
|
240
|
+
]}>
|
|
241
|
+
<View style={styles.nodeCheckboxAndArrowRow}>
|
|
242
|
+
<CheckboxComponent
|
|
243
|
+
text={node.value.name}
|
|
244
|
+
onValueChange={_onCheck}
|
|
245
|
+
value={value.value}
|
|
246
|
+
{...checkBoxViewStyleProps} />
|
|
247
|
+
|
|
248
|
+
{node.value.children?.length ? (
|
|
249
|
+
<ExpandCollapseTouchableComponent
|
|
250
|
+
style={styles.nodeExpandableArrowTouchable}
|
|
251
|
+
onPress={_onToggleExpand}>
|
|
252
|
+
<ExpandCollapseIconComponent
|
|
253
|
+
isExpanded={isExpanded.value}
|
|
254
|
+
/>
|
|
255
|
+
</ExpandCollapseTouchableComponent>
|
|
256
|
+
) : null}
|
|
257
|
+
</View>
|
|
258
|
+
</View>
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const styles = StyleSheet.create({
|
|
263
|
+
defaultHeaderFooter: {
|
|
264
|
+
padding: 5
|
|
265
|
+
},
|
|
266
|
+
nodeParentView: {
|
|
267
|
+
flex: 1
|
|
268
|
+
},
|
|
269
|
+
nodeExpandableArrowTouchable: {
|
|
270
|
+
flex: 1
|
|
271
|
+
},
|
|
272
|
+
nodeCheckboxAndArrowRow: {
|
|
273
|
+
flexDirection: 'row',
|
|
274
|
+
alignItems: 'center',
|
|
275
|
+
minWidth: "100%",
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { TreeNode } from "../types/treeView.types";
|
|
2
|
+
import {
|
|
3
|
+
expanded,
|
|
4
|
+
globalData,
|
|
5
|
+
nodeMap
|
|
6
|
+
} from "../signals/global.signals";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Toggle the expanded state of a tree node by its ID.
|
|
10
|
+
*
|
|
11
|
+
* If the node is currently expanded, it and its descendants will be collapsed.
|
|
12
|
+
* If it is currently collapsed, it will be expanded.
|
|
13
|
+
*
|
|
14
|
+
* @param id - The ID of the tree node to toggle.
|
|
15
|
+
*/
|
|
16
|
+
export function handleToggleExpand(id: string) {
|
|
17
|
+
// Create a new Set based on the current expanded state
|
|
18
|
+
const newExpanded = new Set(expanded.value);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Recursively deletes a node and its descendants from the expanded set.
|
|
22
|
+
*
|
|
23
|
+
* @param node - The tree node to start deleting from.
|
|
24
|
+
*/
|
|
25
|
+
function deleteChildrenFromExpanded(node: TreeNode) {
|
|
26
|
+
if (node.children) {
|
|
27
|
+
for (let child of node.children) {
|
|
28
|
+
newExpanded.delete(child.id);
|
|
29
|
+
deleteChildrenFromExpanded(child);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Finds a node in the tree by its ID.
|
|
36
|
+
*
|
|
37
|
+
* @param nodes - The array of tree nodes to search through.
|
|
38
|
+
* @returns The found tree node, or undefined if not found.
|
|
39
|
+
*/
|
|
40
|
+
function findNode(nodes: TreeNode[]): TreeNode | undefined {
|
|
41
|
+
for (let node of nodes) {
|
|
42
|
+
if (node.id === id) {
|
|
43
|
+
return node;
|
|
44
|
+
} else if (node.children) {
|
|
45
|
+
const found = findNode(node.children);
|
|
46
|
+
if (found) {
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Find the node to expand or collapse
|
|
55
|
+
const node = findNode(globalData.value);
|
|
56
|
+
|
|
57
|
+
if (expanded.value.has(id)) {
|
|
58
|
+
// If the node is currently expanded, collapse it and its descendants
|
|
59
|
+
newExpanded.delete(id);
|
|
60
|
+
if (node) {
|
|
61
|
+
deleteChildrenFromExpanded(node);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
// If the node is currently collapsed, expand it
|
|
65
|
+
newExpanded.add(id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Set the new expanded state
|
|
69
|
+
expanded.value = newExpanded;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Expand all nodes in the tree.
|
|
74
|
+
*/
|
|
75
|
+
export function expandAll() {
|
|
76
|
+
// Create a new Set containing the IDs of all nodes
|
|
77
|
+
const newExpanded = new Set(nodeMap.value.keys());
|
|
78
|
+
expanded.value = newExpanded;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Collapse all nodes in the tree.
|
|
83
|
+
*/
|
|
84
|
+
export function collapseAll() {
|
|
85
|
+
// Create an empty Set
|
|
86
|
+
const newExpanded = new Set<string>();
|
|
87
|
+
expanded.value = newExpanded;
|
|
88
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TreeNode } from "../types/treeView.types";
|
|
2
|
+
import {
|
|
3
|
+
childToParentMap,
|
|
4
|
+
nodeMap,
|
|
5
|
+
} from "../signals/global.signals";
|
|
6
|
+
import { toggleCheckboxes } from "./toggleCheckbox.helper";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Initialize the maps for tracking tree nodes and their parent-child relationships.
|
|
10
|
+
*
|
|
11
|
+
* This function is intended to be called once, during component initialization,
|
|
12
|
+
* with the initial tree data and any preselected node IDs.
|
|
13
|
+
*
|
|
14
|
+
* @param initialData - An array of TreeNode objects that represent the initial tree structure.
|
|
15
|
+
* @param preselectedIds - An optional array of TreeNode IDs that should be preselected.
|
|
16
|
+
*/
|
|
17
|
+
export function initializeNodeMaps(
|
|
18
|
+
initialData: TreeNode[],
|
|
19
|
+
preselectedIds: string[] = [],
|
|
20
|
+
) {
|
|
21
|
+
/**
|
|
22
|
+
* Recursively processes nodes, adding them to the nodeMap and childToParentMap.
|
|
23
|
+
*
|
|
24
|
+
* @param nodes - An array of TreeNode objects to be processed.
|
|
25
|
+
* @param parentId - The ID of the parent node, if applicable.
|
|
26
|
+
*/
|
|
27
|
+
const processNodes = (
|
|
28
|
+
nodes: TreeNode[],
|
|
29
|
+
parentId: string | null = null
|
|
30
|
+
) => {
|
|
31
|
+
nodes.forEach((node) => {
|
|
32
|
+
// Each node is added to the nodeMap with its ID as the key
|
|
33
|
+
nodeMap.value.set(node.id, node);
|
|
34
|
+
// If the node has a parent, its ID is mapped to the parent's ID in the childToParentMap
|
|
35
|
+
if (parentId) childToParentMap.value.set(node.id, parentId);
|
|
36
|
+
// If the node has children, recursively process them
|
|
37
|
+
if (node.children) processNodes(node.children, node.id);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Begin processing with the initial tree data
|
|
42
|
+
processNodes(initialData);
|
|
43
|
+
|
|
44
|
+
// Check any preselected nodes
|
|
45
|
+
toggleCheckboxes(preselectedIds, true);
|
|
46
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TreeNode } from "../types/treeView.types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a given tree node contains a specific search term in any of its specified keys.
|
|
5
|
+
*
|
|
6
|
+
* This function will check each of the specified keys in the tree node, convert the key's value to a string,
|
|
7
|
+
* and check if it includes the search term.
|
|
8
|
+
*
|
|
9
|
+
* @param node - The tree node to search through.
|
|
10
|
+
* @param searchTerm - The term to search for.
|
|
11
|
+
* @param searchKeys - The keys in the tree node to search in.
|
|
12
|
+
* @returns True if the search term is found in any of the specified keys, false otherwise.
|
|
13
|
+
*/
|
|
14
|
+
export function doesNodeContainSearchTerm(
|
|
15
|
+
node: TreeNode,
|
|
16
|
+
searchTerm: string,
|
|
17
|
+
searchKeys: string[]
|
|
18
|
+
): boolean {
|
|
19
|
+
// We're using the `some` method on the array of keys to check each one
|
|
20
|
+
return searchKeys.some(key => {
|
|
21
|
+
// Get the value of the key in the tree node
|
|
22
|
+
const nodeValue = node[key];
|
|
23
|
+
// Check if the string representation of the key's value includes the search term
|
|
24
|
+
// If the value is undefined or null, `nodeValue?.toString()` will return undefined,
|
|
25
|
+
// and the call to `toLowerCase().includes(searchTerm)` will return false.
|
|
26
|
+
return (nodeValue?.toString().toLowerCase().includes(searchTerm));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
innerMostChildrenIds,
|
|
3
|
+
nodeMap,
|
|
4
|
+
searchText,
|
|
5
|
+
state
|
|
6
|
+
} from "../signals/global.signals";
|
|
7
|
+
import { toggleCheckboxes } from "./toggleCheckbox.helper";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Selects all nodes that are currently visible due to the applied filter.
|
|
11
|
+
*
|
|
12
|
+
* If there is no search text, then it selects all nodes; otherwise, it selects all visible nodes.
|
|
13
|
+
*/
|
|
14
|
+
export function selectAllFiltered() {
|
|
15
|
+
// If there's no search text, select all nodes
|
|
16
|
+
if (!searchText.value) {
|
|
17
|
+
selectAll();
|
|
18
|
+
} else {
|
|
19
|
+
// If there's search text, only select the visible nodes
|
|
20
|
+
toggleCheckboxes(innerMostChildrenIds.value, true);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Unselects all nodes that are currently visible due to the applied filter.
|
|
26
|
+
*
|
|
27
|
+
* If there is no search text, then it unselects all nodes; otherwise, it unselects all visible nodes.
|
|
28
|
+
*/
|
|
29
|
+
export function unselectAllFiltered() {
|
|
30
|
+
// If there's no search text, unselect all nodes
|
|
31
|
+
if (!searchText.value) {
|
|
32
|
+
unselectAll();
|
|
33
|
+
} else {
|
|
34
|
+
// If there's search text, only unselect the visible nodes
|
|
35
|
+
toggleCheckboxes(innerMostChildrenIds.value, false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Selects all nodes in the tree.
|
|
41
|
+
*
|
|
42
|
+
* This function selects all nodes by adding all node ids to the checked set and clearing the indeterminate set.
|
|
43
|
+
*/
|
|
44
|
+
export function selectAll() {
|
|
45
|
+
// Create a new set containing the ids of all nodes
|
|
46
|
+
const newChecked = new Set(nodeMap.value.keys());
|
|
47
|
+
// Update the state to mark all nodes as checked
|
|
48
|
+
state.value = ({ checked: newChecked, indeterminate: new Set() });
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Unselects all nodes in the tree.
|
|
53
|
+
*
|
|
54
|
+
* This function unselects all nodes by clearing both the checked and indeterminate sets.
|
|
55
|
+
*/
|
|
56
|
+
export function unselectAll() {
|
|
57
|
+
// Update the state to mark all nodes as unchecked
|
|
58
|
+
state.value = ({ checked: new Set(), indeterminate: new Set() });
|
|
59
|
+
};
|