@@ -33,6 +33,7 @@ import {
3333 noop ,
3434 isBranchNotSelectedAndHasOnlySelectedChild ,
3535 getOnSelectTreeAction ,
36+ getBranchNodesToExpand ,
3637} from "./utils" ;
3738import { Node } from "./node" ;
3839import {
@@ -62,6 +63,7 @@ interface IUseTreeProps {
6263 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6364 onLoadData ?: ( props : ITreeViewOnLoadDataProps ) => Promise < any > ;
6465 togglableSelect ?: boolean ;
66+ focusedId ?: NodeId ;
6567}
6668
6769const useTree = ( {
@@ -82,6 +84,7 @@ const useTree = ({
8284 propagateSelect,
8385 propagateSelectUpwards,
8486 treeRef,
87+ focusedId,
8588} : IUseTreeProps ) => {
8689 const treeParentNode = getTreeParent ( data ) ;
8790 const [ state , dispatch ] = useReducer ( treeReducer , {
@@ -419,19 +422,47 @@ const useTree = ({
419422 nodeRefs ?. current != null &&
420423 leafRefs ?. current != null
421424 ) {
422- const isTreeActive = ( treeRef ?. current == null ) ||
423- ( document . activeElement && treeRef . current . contains ( document . activeElement ) ) ;
424- if ( isTreeActive ) {
425+ const isTreeActive =
426+ treeRef ?. current == null ||
427+ ( document . activeElement &&
428+ treeRef . current . contains ( document . activeElement ) ) ;
429+ if ( isTreeActive || focusedId ) {
425430 // Only scroll and focus on the tree when it is the active element on the page.
426431 // This prevents controlled updates from scrolling to the tree and giving it focus.
427432 const tabbableNode = nodeRefs . current [ tabbableId ] ;
428- const leafNode = leafRefs . current [ lastInteractedWith ] ;
433+ const leafNode = leafRefs . current [ lastInteractedWith ] ;
429434 scrollToRef ( leafNode ) ;
430435 focusRef ( tabbableNode ) ;
431436 }
432437 }
433438 } , [ tabbableId , nodeRefs , leafRefs , lastInteractedWith ] ) ;
434439
440+ //Controlled focus
441+ useEffect ( ( ) => {
442+ if ( ! focusedId ) {
443+ dispatch ( {
444+ type : treeTypes . clearFocus ,
445+ id : treeParentNode . children [ 0 ] ,
446+ } ) ;
447+ }
448+
449+ if ( focusedId && data . find ( ( node ) => node . id === focusedId ) ) {
450+ const nodesToExpand = getBranchNodesToExpand ( data , focusedId ) ;
451+ if ( nodesToExpand . length ) {
452+ dispatch ( {
453+ type : treeTypes . expandMany ,
454+ ids : nodesToExpand ,
455+ lastInteractedWith : focusedId ,
456+ } ) ;
457+ }
458+ dispatch ( {
459+ type : treeTypes . focus ,
460+ id : focusedId ,
461+ lastInteractedWith : focusedId ,
462+ } ) ;
463+ }
464+ } , [ focusedId ] ) ;
465+
435466 // The "as const" technique tells Typescript that this is a tuple not an array
436467 return [ state , dispatch ] as const ;
437468} ;
@@ -518,6 +549,8 @@ export interface ITreeViewProps {
518549 treeState : ITreeViewState ;
519550 dispatch : React . Dispatch < TreeViewAction > ;
520551 } ) => void ;
552+ /** Id of the node to focus */
553+ focusedId ?: NodeId ;
521554}
522555
523556const TreeView = React . forwardRef < HTMLUListElement , ITreeViewProps > (
@@ -543,6 +576,7 @@ const TreeView = React.forwardRef<HTMLUListElement, ITreeViewProps>(
543576 clickAction = clickActions . select ,
544577 nodeAction = "select" ,
545578 expandedIds,
579+ focusedId,
546580 onBlur,
547581 ...other
548582 } ,
@@ -572,7 +606,8 @@ const TreeView = React.forwardRef<HTMLUListElement, ITreeViewProps>(
572606 multiSelect,
573607 propagateSelect,
574608 propagateSelectUpwards,
575- treeRef : innerRef ,
609+ treeRef : innerRef ,
610+ focusedId,
576611 } ) ;
577612 propagateSelect = propagateSelect && multiSelect ;
578613
@@ -1006,6 +1041,9 @@ TreeView.propTypes = {
10061041
10071042 /** Function called to load data asynchronously on expand */
10081043 onLoadData : PropTypes . func ,
1044+
1045+ /** Id of the node to focus on */
1046+ focusedId : PropTypes . oneOfType ( [ PropTypes . string , PropTypes . number ] ) ,
10091047} ;
10101048
10111049export default TreeView ;
0 commit comments