| 109 | * using the Select component. |
| 110 | */ |
| 111 | export function TreeSelect<T>({ |
| 112 | nodes, |
| 113 | onSelect, |
| 114 | onCancel, |
| 115 | onFocus, |
| 116 | focusNodeId, |
| 117 | visibleOptionCount, |
| 118 | layout = 'expanded', |
| 119 | isDisabled = false, |
| 120 | hideIndexes = false, |
| 121 | isNodeExpanded, |
| 122 | onExpand, |
| 123 | onCollapse, |
| 124 | getParentPrefix, |
| 125 | getChildPrefix, |
| 126 | onUpFromFirstItem, |
| 127 | }: TreeSelectProps<T>): React.ReactNode { |
| 128 | // Track which nodes are expanded (internal state if not controlled externally) |
| 129 | const [internalExpandedIds, setInternalExpandedIds] = React.useState<Set<string | number>>(new Set()); |
| 130 | |
| 131 | // Track if we're programmatically setting focus to avoid infinite loops |
| 132 | const isProgrammaticFocusRef = React.useRef(false); |
| 133 | |
| 134 | // Track last focused ID to prevent duplicate focus calls |
| 135 | const lastFocusedIdRef = React.useRef<string | number | null>(null); |
| 136 | |
| 137 | // Determine if a node is expanded (use external function if provided, otherwise use internal state) |
| 138 | const isExpanded = React.useCallback( |
| 139 | (nodeId: string | number): boolean => { |
| 140 | if (isNodeExpanded) { |
| 141 | return isNodeExpanded(nodeId); |
| 142 | } |
| 143 | return internalExpandedIds.has(nodeId); |
| 144 | }, |
| 145 | [isNodeExpanded, internalExpandedIds], |
| 146 | ); |
| 147 | |
| 148 | // Flatten the tree into a linear list for the Select component |
| 149 | const flattenedNodes = React.useMemo((): FlattenedNode<T>[] => { |
| 150 | const result: FlattenedNode<T>[] = []; |
| 151 | |
| 152 | function traverse(node: TreeNode<T>, depth: number, parentId?: string | number): void { |
| 153 | const hasChildren = !!node.children && node.children.length > 0; |
| 154 | const nodeIsExpanded = isExpanded(node.id); |
| 155 | |
| 156 | result.push({ |
| 157 | node, |
| 158 | depth, |
| 159 | isExpanded: nodeIsExpanded, |
| 160 | hasChildren, |
| 161 | parentId, |
| 162 | }); |
| 163 | |
| 164 | // Only traverse children if this node is expanded |
| 165 | if (hasChildren && nodeIsExpanded && node.children) { |
| 166 | for (const child of node.children) { |
| 167 | traverse(child, depth + 1, node.id); |
| 168 | } |