Skip to content

Commit 346025b

Browse files
authored
Merge pull request #110 from JairajJangle/feat/scroll-to-node-id
Added feature to scroll to node and ref function to get underlying child to parent map
2 parents 00c6369 + e1ff688 commit 346025b

10 files changed

+836
-196
lines changed

README.md

+52-24
Original file line numberDiff line numberDiff line change
@@ -115,35 +115,45 @@ export function TreeViewUsageExample(){
115115

116116
### Properties
117117

118+
#### TreeViewProps`<ID = string>`
119+
120+
*The `TreeViewProps` interface defines the properties for the tree view component.*
121+
118122
| Property | Type | Required | Description |
119123
| ---------------------------------- | ------------------------------------------------------------ | -------- | ------------------------------------------------------------ |
120-
| `data` | [TreeNode](#treenode) | Yes | An array of `TreeNode` objects |
121-
| `onCheck` | `(checkedIds: string[], indeterminateIds: string[]) => void` | No | Callback when a checkbox state changes |
122-
| `onExpand` | `(expandedIds: string[]) => void` | No | Callback when a node is expanded |
123-
| `preselectedIds` | `string[]` | No | An array of `id`s that should be pre-selected |
124-
| `preExpandedIds` | `string[]` | No | An array of `id`s that should be pre-expanded |
124+
| `data` | [TreeNode](#treenode)`<ID = string>[]` | Yes | An array of `TreeNode` objects |
125+
| `onCheck` | `(checkedIds: ID[], indeterminateIds: ID[]) => void` | No | Callback when a checkbox state changes |
126+
| `onExpand` | `(expandedIds: ID[]) => void` | No | Callback when a node is expanded |
127+
| `preselectedIds` | `ID[]` | No | An array of `id`s that should be pre-selected |
128+
| `preExpandedIds` | `ID[]` | No | An array of `id`s that should be pre-expanded |
125129
| `selectionPropagation` | [SelectionPropagation](#selectionpropagation) | No | Control Selection Propagation Behavior. Choose whether you want to auto-select children or parents. |
130+
| `initialScrollNodeID` | `ID` | No | Set node ID to scroll to intiially on tree view render. |
126131
| `indentationMultiplier` | `number` | No | Indentation (`marginStart`) per level (defaults to 15) |
127132
| `treeFlashListProps` | [TreeFlatListProps](#treeflatlistprops) | No | Props for the flash list |
128133
| `checkBoxViewStyleProps` | [BuiltInCheckBoxViewStyleProps](#builtincheckboxviewstyleprops) | No | Props for the checkbox view |
129134
| `CheckboxComponent` | `ComponentType<`[CheckBoxViewProps](#checkboxviewprops)`>` | No | A custom checkbox component. Defaults to React Native Paper's Checkbox |
130135
| `ExpandCollapseIconComponent` | `ComponentType<`[ExpandIconProps](#expandiconprops)`>` | No | A custom expand/collapse icon component |
131136
| `ExpandCollapseTouchableComponent` | `ComponentType<`[TouchableOpacityProps](https://reactnative.dev/docs/touchableopacity#props)`>` | No | A custom expand/collapse touchable component |
132-
| `CustomNodeRowComponent` | `React.ComponentType<`[NodeRowProps](#noderowprops)`>` | No | Custom row item component |
133-
134-
ℹ️ If `CustomNodeRowComponent` is provided then below props are not applied:
135-
136-
1. `indentationMultiplier`
137-
2. `checkBoxViewStyleProps`
138-
3. `CheckboxComponent`
139-
4. `ExpandCollapseIconComponent`
140-
5. `ExpandCollapseTouchableComponent`.
141-
142-
⚠️ If the tree view doesn't scroll if rendered in a complex nested scroll view/s then try setting the `renderScrollComponent` value in `treeFlashListProps` to `ScrollView` from `react-native-gesture-handler`.
137+
| `CustomNodeRowComponent` | `React.ComponentType<`[NodeRowProps](#noderowprops)`<ID>>` | No | Custom row item component |
138+
139+
##### Notes
140+
141+
- The `ID` type parameter allows flexibility in specifying the type of node identifiers (e.g., `string`, `number`, or custom types).
142+
- ℹ️ If `CustomNodeRowComponent` is provided then below props are not applied:
143+
1. `indentationMultiplier`
144+
1. `checkBoxViewStyleProps`
145+
1. `CheckboxComponent`
146+
1. `BuiltInCheckBoxViewStyleProps`
147+
1. `ExpandCollapseIconComponent`
148+
1. `ExpandCollapseTouchableComponent`.
149+
150+
- ⚠️ If the tree view doesn't scroll if rendered in a complex nested scroll view/s then try setting the `renderScrollComponent` value in `treeFlashListProps` to `ScrollView` from `react-native-gesture-handler`.
143151

144152
---
145153

146-
#### TreeNode
154+
#### TreeNode`<ID = string>`
155+
156+
*The `TreeNode` interface defines the properties for individual item of the tree view*
147157

148158
| Property | Type | Required | Description |
149159
| --------------- | ------------------------ | -------- | ------------------------------------------------------------ |
@@ -154,7 +164,9 @@ export function TreeViewUsageExample(){
154164

155165
---
156166

157-
#### TreeViewRef
167+
#### TreeViewRef`<ID = string>`
168+
169+
*The `TreeViewRef` interface defines the properties for the ref object of the tree view component*
158170

159171
| Property | Type | Description |
160172
| --------------------- | ----------------------------------------------------- | ------------------------------------------------------------ |
@@ -164,16 +176,30 @@ export function TreeViewUsageExample(){
164176
| `unselectAllFiltered` | `() => void` | Unselects all **filtered** nodes |
165177
| `expandAll` | `() => void` | Expands all nodes |
166178
| `collapseAll` | `() => void` | Collapses all nodes |
167-
| `expandNodes` | `(ids: string[]) => void` | Expands specified nodes |
168-
| `collapseNodes` | `(ids: string[]) => void` | Collapses specified nodes |
169-
| `selectNodes` | `(ids: string[]) => void` | Selects specified nodes |
170-
| `unSelectNodes` | `(ids: string[]) => void` | Unselects specified nodes |
179+
| `expandNodes` | `(ids: ID[]) => void` | Expands specified nodes |
180+
| `collapseNodes` | `(ids: ID[]) => void` | Collapses specified nodes |
181+
| `selectNodes` | `(ids: ID[]) => void` | Selects specified nodes |
182+
| `unSelectNodes` | `(ids: ID[]) => void` | Unselects specified nodes |
171183
| `setSearchText` | `(searchText: string, searchKeys?: string[]) => void` | Set the search text and optionally the search keys. Default search key is "name"<br /><br />Recommended to call this inside a debounced function if you find any performance issue otherwise. |
184+
| `scrollToNodeID` | `(params: `[ScrollToNodeParams](#scrolltonodeparams)`<ID>) => void;` | Scrolls the tree view to the node of the specified ID. |
185+
| `getChildToParentMap` | `() => Map<ID, ID>` | Get the child to parent tree view map. |
186+
187+
#### ScrollToNodeParams
188+
| Property | Type | Required | Description |
189+
| -------------------- | --------- | -------- | ------------------------------------------------------------ |
190+
| `nodeId` | `ID` | Yes | Node ID to expand and scroll to. |
191+
| `expandScrolledNode` | `boolean` | No | Whether to expand scrolled node to reveal its children. Defaults to `false`. |
192+
| `animated` | `boolean` | No | Control if scrolling should be animated. |
193+
| `viewOffset` | `number` | No | A fixed number of pixels to offset the scrolled node position. |
194+
| `viewPosition` | `number` | No | A value of `0` places the scrolled node item at the top, `1` at the bottom, and `0.5` centered in the middle. |
195+
172196

173197
---
174198

175199
#### SelectionPropagation
176200

201+
*The `SelectionPropagation` interface defines the selection propagation behaviour of the tree view*
202+
177203
| Property | Type | Required | Description |
178204
| ------------ | --------- | -------- | ------------------------------------------------------------ |
179205
| `toChildren` | `boolean` | No | Whether to propagate selection to children nodes. Defaults to `true`. |
@@ -183,12 +209,14 @@ export function TreeViewUsageExample(){
183209

184210
#### TreeFlatListProps
185211

186-
All properties of `FlashListProps`(from `@shopify/flash-list`) except for `data` and `renderItem`
212+
*All properties of `FlashListProps`(from `@shopify/flash-list`) except for `data` and `renderItem`*
187213

188214
---
189215

190216
#### BuiltInCheckBoxViewStyleProps
191217

218+
*This interface allows you to customize the style of the built-in checkbox component that is rendered in the tree view by default. Overriden if `CustomNodeRowComponent` is used.*
219+
192220
| Property | Type | Required | Description |
193221
| -------------------------- | -------------------------------- | -------- | ------------------------------------------------------ |
194222
| `outermostParentViewStyle` | `StyleProp<ViewStyle>` | No | Optional style modifier for the outermost parent view. |
@@ -225,7 +253,7 @@ Type: `boolean` OR `"indeterminate"`
225253

226254
---
227255

228-
#### NodeRowProps
256+
#### NodeRowProps`<ID = string>`
229257

230258
| Property | Type | Required | Description |
231259
| -------------- | --------------------------------------- | -------- | ------------------------------------------------------- |

example/src/App.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import packageJson from '../../package.json';
1919
import { TwoTreeViewsScreen } from "./screens/TwoTreeViewsScreen";
2020
import CustomNodeID from './screens/CustomNodeIDScreen';
21+
import ControlsDemoScreen from "./screens/ControlsDemoScreen";
2122

2223
const data: ShowcaseExampleScreenSectionType[] = [
2324
{
@@ -37,6 +38,11 @@ const data: ShowcaseExampleScreenSectionType[] = [
3738
name: 'Large Data',
3839
slug: 'large-data',
3940
getScreen: () => LargeDataScreen,
41+
},
42+
{
43+
name: 'Adv. Controls',
44+
slug: 'controls',
45+
getScreen: () => ControlsDemoScreen,
4046
}
4147
],
4248
},
+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import * as React from 'react';
2+
3+
import {
4+
Button,
5+
Modal,
6+
SafeAreaView,
7+
View,
8+
Text,
9+
TextInput,
10+
Switch
11+
} from 'react-native';
12+
13+
import {
14+
TreeView,
15+
type TreeViewRef
16+
} from 'react-native-tree-multi-select';
17+
18+
import { styles } from './screens.styles';
19+
import {
20+
defaultID,
21+
generateTreeList
22+
} from '../utils/sampleDataGenerator';
23+
import debounce from "lodash/debounce";
24+
25+
export default function ControlsDemoScreen() {
26+
const sampleData = React.useRef(generateTreeList(200, 5, 6, defaultID, "1"));
27+
const treeViewRef = React.useRef<TreeViewRef | null>(null);
28+
const onSubmitFnRef = React.useRef<() => void>(() => { });
29+
30+
const [dialogTitle, setDialogTitle] = React.useState("Enter Text");
31+
const [placeholder, setPlaceholder] = React.useState("Type here");
32+
33+
const [
34+
showExpandScrolledNodeOption,
35+
setShowExpandScrolledNodeOption
36+
] = React.useState(false);
37+
const [expandScrolledNode, setExpandScrolledNode] = React.useState(false);
38+
39+
const latestExpandScrolledNode = React.useRef(expandScrolledNode);
40+
React.useEffect(() => {
41+
latestExpandScrolledNode.current = expandScrolledNode;
42+
}, [expandScrolledNode]);
43+
44+
const [visible, setVisible] = React.useState(false);
45+
const input = React.useRef("");
46+
47+
React.useEffect(() => {
48+
if (!visible)
49+
setShowExpandScrolledNodeOption(false);
50+
}, [visible]);
51+
52+
// eslint-disable-next-line react-hooks/exhaustive-deps
53+
const debouncedSetInput = React.useCallback(
54+
debounce((text) => {
55+
input.current = text;
56+
}, 100, { trailing: true, leading: false }),
57+
[]
58+
);
59+
60+
function expandNodesPressed() {
61+
setDialogTitle("Enter Node IDs to Expand");
62+
setPlaceholder("Comma Separated Node IDs");
63+
setVisible(true);
64+
onSubmitFnRef.current = expandNodes;
65+
}
66+
function expandNodes() {
67+
const nodeIds = input.current.split(',').map(id => id.trim());
68+
treeViewRef.current?.expandNodes(nodeIds);
69+
70+
setVisible(false);
71+
onSubmitFnRef.current = () => { };
72+
};
73+
74+
function collapseNodesPressed() {
75+
setDialogTitle("Enter Node IDs to Collapse");
76+
setPlaceholder("Comma Separated Node IDs");
77+
setVisible(true);
78+
onSubmitFnRef.current = collapseNodes;
79+
}
80+
function collapseNodes() {
81+
const nodeIds = input.current.split(',').map(id => id.trim());
82+
treeViewRef.current?.collapseNodes(nodeIds);
83+
84+
setVisible(false);
85+
onSubmitFnRef.current = () => { };
86+
};
87+
88+
function selectNodesPressed() {
89+
setDialogTitle("Enter Node IDs to Select");
90+
setPlaceholder("Comma Separated Node IDs");
91+
setVisible(true);
92+
onSubmitFnRef.current = selectNodes;
93+
}
94+
function selectNodes() {
95+
const nodeIds = input.current.split(',').map(id => id.trim());
96+
treeViewRef.current?.selectNodes(nodeIds);
97+
98+
setVisible(false);
99+
onSubmitFnRef.current = () => { };
100+
};
101+
102+
function unselectNodesPressed() {
103+
setDialogTitle("Enter Node IDs to Unselect");
104+
setPlaceholder("Comma Separated Node IDs");
105+
setVisible(true);
106+
onSubmitFnRef.current = unselectNodes;
107+
}
108+
function unselectNodes() {
109+
const nodeIds = input.current.split(',').map(id => id.trim());
110+
treeViewRef.current?.unselectNodes(nodeIds);
111+
112+
setVisible(false);
113+
onSubmitFnRef.current = () => { };
114+
};
115+
116+
function scrollToNodeIDPressed() {
117+
setDialogTitle("Enter Node ID to Scroll To");
118+
setPlaceholder("Enter Single Node ID");
119+
setShowExpandScrolledNodeOption(true);
120+
setVisible(true);
121+
onSubmitFnRef.current = scrollToNodeID;
122+
}
123+
function scrollToNodeID() {
124+
const nodeId = input.current.trim();
125+
treeViewRef.current?.scrollToNodeID({
126+
nodeId,
127+
animated: true,
128+
expandScrolledNode: latestExpandScrolledNode.current
129+
});
130+
131+
setVisible(false);
132+
onSubmitFnRef.current = () => { };
133+
}
134+
135+
function printChildToParentMap() {
136+
const childToParentMap = treeViewRef.current?.getChildToParentMap();
137+
console.log("Child to parent map:", childToParentMap);
138+
}
139+
140+
function onDialogCancel() {
141+
setVisible(false);
142+
onSubmitFnRef.current = () => { };
143+
}
144+
145+
function onDialogSubmit() {
146+
setVisible(false);
147+
onSubmitFnRef.current();
148+
}
149+
150+
return (
151+
<SafeAreaView
152+
style={styles.mainView}>
153+
154+
<Modal
155+
visible={visible}
156+
transparent
157+
animationType={"fade"}>
158+
<View style={styles.modalContainer}>
159+
<View style={styles.dialogBox}>
160+
<Text style={styles.title}>{dialogTitle}</Text>
161+
<TextInput
162+
autoCorrect={false}
163+
autoFocus={true}
164+
style={styles.input}
165+
placeholder={placeholder}
166+
onChangeText={debouncedSetInput} />
167+
168+
{showExpandScrolledNodeOption && (
169+
<View style={styles.expandScrolledNodeOptionView}>
170+
<Text style={styles.expandScrolledNodeOptionText}>
171+
Expand scrolled node to show its children?
172+
</Text>
173+
<Switch
174+
value={expandScrolledNode}
175+
onValueChange={setExpandScrolledNode} />
176+
</View>
177+
)}
178+
179+
<View style={styles.buttonContainer}>
180+
<Button title="Cancel" onPress={onDialogCancel} />
181+
<Button title="Submit" onPress={onDialogSubmit} />
182+
</View>
183+
</View>
184+
</View>
185+
</Modal>
186+
187+
<View
188+
style={styles.selectionButtonRow}>
189+
<Button
190+
title='Expand Nodes'
191+
onPress={expandNodesPressed} />
192+
<Button
193+
title='Collapse Nodes'
194+
onPress={collapseNodesPressed} />
195+
</View>
196+
<View
197+
style={styles.selectionButtonRow}>
198+
<Button
199+
title='Select Nodes'
200+
onPress={selectNodesPressed} />
201+
<Button
202+
title='Unselect Nodes'
203+
onPress={unselectNodesPressed} />
204+
</View>
205+
<View
206+
style={styles.selectionButtonRow}>
207+
<Button
208+
title='Scroll To Node'
209+
onPress={scrollToNodeIDPressed} />
210+
211+
<Button
212+
title='Print Child to Parent Map'
213+
onPress={printChildToParentMap} />
214+
</View>
215+
<View
216+
style={styles.treeViewParent}>
217+
<TreeView
218+
ref={treeViewRef}
219+
data={sampleData.current} />
220+
</View>
221+
</SafeAreaView>
222+
);
223+
}

0 commit comments

Comments
 (0)