packageDocumentation module import { take } from "rxjs/internal/operators/take" ; import { Subject } from "rxjs/internal/Subject" ; import { BeUiEvent } from "@itwin/core-bentley" ; import { MultiSelectionHandler , SelectionHandler , SingleSelectionHandler } from "../../../common/selection/SelectionHandler" ; import { SelectionMode } from "../../../common/selection/SelectionModes" ; import { Observable } from "../Observable" ; import { TreeActions } from "../TreeActions" ; import { isTreeModelNode , TreeModelNode , VisibleTreeNodes } from "../TreeModel" ; import { isNavigationKey , ItemKeyboardNavigator , Orientation } from "@itwin/core-react" ; internal export interface SelectionReplacementEvent { selectedNodeIds : IndividualSelection | RangeSelection ; } internal export interface SelectionModificationEvent { selectedNodes : IndividualSelection | RangeSelection ; deselectedNodes : IndividualSelection ; } internal export interface DragSelectionStartEvent { selectionChanges : Observable < SelectionModificationEvent > ; } internal export type IndividualSelection = string [ ] ; internal export interface RangeSelection { from : string ; to : string ; } internal export function isRangeSelection ( selection ) { return selection && typeof ( selection . from ) === "string" && typeof ( selection . to ) === "string" ; } internal export class TreeSelectionManager implements Pick < TreeActions , "onNodeClicked" | "onNodeMouseDown" | "onNodeMouseMove" > { private _selectionHandler : SelectionHandler < Selection > ; private _dragSelectionOperation ? : Subject < SelectionModificationEvent > ; private _itemHandlers : Array < Array < SingleSelectionHandler < string > > > ; private _getVisibleNodes : ( ( ) => VisibleTreeNodes ) | undefined ; public onSelectionChanged = new BeUiEvent < SelectionModificationEvent > ( ) ; public onSelectionReplaced = new BeUiEvent < SelectionReplacementEvent > ( ) ; public onDragSelection = new BeUiEvent < DragSelectionStartEvent > ( ) ; constructor ( selectionMode , getVisibleNodes ? ) { this . _getVisibleNodes = getVisibleNodes ; const onItemsSelected = ( selections , replacement ) => { if ( isIndividualSelection ( selections [ 0 ] ) ) { if ( replacement ) { this . onSelectionReplaced . emit ( { selectedNodeIds : selections as IndividualSelection } ) ; } else { this . onSelectionChanged . emit ( { selectedNodes : selections as IndividualSelection , deselectedNodes : [ ] } ) ; } } else { if ( replacement ) { this . onSelectionReplaced . emit ( { selectedNodeIds : selections [ 0 ] } ) ; } else { this . onSelectionChanged . emit ( { selectedNodes : selections [ 0 ] , deselectedNodes : [ ] } ) ; } } } ; const onItemsDeselected = ( deselections ) => { this . onSelectionChanged . emit ( { selectedNodes : [ ] , deselectedNodes : deselections as IndividualSelection } ) ; } ; this . _selectionHandler = new SelectionHandler ( selectionMode , onItemsSelected , onItemsDeselected ) ; const _this = this ; const itemHandlers = new Proxy ( { } , { get ( _target , prop ) { if ( prop === "length" ) { return _this . _getVisibleNodes === undefined ? 0 : _this . _getVisibleNodes ( ) . getNumNodes ( ) ; } if ( _this . _getVisibleNodes === undefined ) return undefined ; const index = + ( prop as string ) ; const node = _this . getVisibleNodeAtIndex ( _this . _getVisibleNodes ( ) , index ) ; return new ItemHandler ( node ) ; } } ) as Array < SingleSelectionHandler < string > > ; this . _itemHandlers = [ itemHandlers ] ; } private getVisibleNodeAtIndex ( nodes , index ) { const foundNode = nodes !== undefined ? nodes . getAtIndex ( index ) : undefined ; return isTreeModelNode ( foundNode ) ? foundNode : undefined ; } public setVisibleNodes ( visibleNodes ) { this . _getVisibleNodes = visibleNodes ; } public onNodeClicked ( nodeId , event ) { const selectionFunc = this . _selectionHandler . createSelectionFunction ( ... this . createSelectionHandlers ( nodeId ) ) ; selectionFunc ( event . shiftKey , event . ctrlKey ) ; } public onNodeMouseDown ( nodeId ) { this . _selectionHandler . createDragAction ( this . createSelectionHandlers ( nodeId ) [ 0 ] , this . _itemHandlers , nodeId ) ; window . addEventListener ( "mouseup" , ( ) => { if ( this . _dragSelectionOperation ) { this . _selectionHandler . completeDragAction ( ) ; this . _dragSelectionOperation . complete ( ) ; this . _dragSelectionOperation = undefined ; } } , { once : true } ) ; this . _dragSelectionOperation = new Subject ( ) ; this . _dragSelectionOperation . pipe ( take ( 1 ) ) . subscribe ( ( value ) => { this . onDragSelection . emit ( { selectionChanges : this . _dragSelectionOperation ! } ) ; this . _dragSelectionOperation ! . next ( value ) ; } ) ; } public onNodeMouseMove ( nodeId ) { this . _selectionHandler . updateDragAction ( nodeId ) ; } private createSelectionHandlers ( nodeId ) { let deselectedAll = false ; const multiSelectionHandler = { selectBetween : ( item1 , item2 ) => [ { from : item1 as string , to : item2 as string } ] , updateSelection : ( selections , deselections ) => { const selectedNodeIds = selections as string [ ] ; const deselectedNodeIds = deselections as string [ ] ; this . _dragSelectionOperation ! . next ( { selectedNodes : selectedNodeIds , deselectedNodes : deselectedNodeIds } ) ; } , deselectAll : ( ) => { deselectedAll = true ; } , areEqual : ( item1 , item2 ) => item1 === item2 } ; const singleSelectionHandler = { preselect : ( ) => { } , select : ( ) => { } , deselect : ( ) => { } , isSelected : ( ) => { if ( deselectedAll || this . _getVisibleNodes === undefined ) { return false ; } const node = this . _getVisibleNodes ( ) . getModel ( ) . getNode ( nodeId ) ; return node !== undefined && node . isSelected ; } , item : ( ) => nodeId } ; return [ multiSelectionHandler , singleSelectionHandler ] ; } public onTreeKeyDown ( event , actions ) { this . _onKeyboardEvent ( event , actions , true ) ; } public onTreeKeyUp ( event , actions ) { this . _onKeyboardEvent ( event , actions , false ) ; } private _onKeyboardEvent = ( e , actions , isKeyDown ) => { if ( isNavigationKey ( e . key ) ) { if ( ! this . _getVisibleNodes ) return ; const processedNodeId = this . _selectionHandler . processedItem ; const isExpandable = ( node ) : boolean => ! node . isLoading && node . numChildren !== 0 ; const isEditing = ( node ) : boolean => node . editingInfo !== undefined ; if ( this . _getVisibleNodes && isIndividualSelection ( processedNodeId ) ) { const processedNode = this . _getVisibleNodes ( ) . getModel ( ) . getNode ( processedNodeId ) ; if ( processedNode && isEditing ( processedNode ) ) return ; } const handleKeyboardSelectItem = ( index ) => { if ( this . _getVisibleNodes ) { const node = this . getVisibleNodeAtIndex ( this . _getVisibleNodes ( ) , index ) ; if ( node ) { if ( isEditing ( node ) ) return ; const selectionFunc = this . _selectionHandler . createSelectionFunction ( ... this . createSelectionHandlers ( node . id ) ) ; selectionFunc ( e . shiftKey , e . ctrlKey ) ; } } } ; const handleKeyboardActivateItem = ( _index ) => { if ( this . _getVisibleNodes && isIndividualSelection ( processedNodeId ) ) { const node = this . _getVisibleNodes ( ) . getModel ( ) . getNode ( processedNodeId ) ; if ( node ) { if ( isExpandable ( node ) ) { if ( ! node . isExpanded ) actions . onNodeExpanded ( node . id ) ; else actions . onNodeCollapsed ( node . id ) ; } else { actions . onNodeEditorActivated ( node . id ) ; } } } } ; const handleCrossAxisArrowKey = ( forward ) => { if ( this . _getVisibleNodes && isIndividualSelection ( processedNodeId ) ) { const node = this . _getVisibleNodes ( ) . getModel ( ) . getNode ( processedNodeId ) ; if ( node ) { if ( isExpandable ( node ) ) { if ( forward && ! node . isExpanded ) actions . onNodeExpanded ( node . id ) ; else if ( ! forward && node . isExpanded ) actions . onNodeCollapsed ( node . id ) ; } } } } ; if ( isIndividualSelection ( processedNodeId ) ) { const itemKeyboardNavigator = new ItemKeyboardNavigator ( handleKeyboardSelectItem , handleKeyboardActivateItem ) ; itemKeyboardNavigator . orientation = Orientation . Vertical ; itemKeyboardNavigator . crossAxisArrowKeyHandler = handleCrossAxisArrowKey ; itemKeyboardNavigator . allowWrap = false ; itemKeyboardNavigator . itemCount = this . _getVisibleNodes ( ) . getNumNodes ( ) ; const index = this . _getVisibleNodes ( ) . getIndexOfNode ( processedNodeId ) ; isKeyDown ? itemKeyboardNavigator . handleKeyDownEvent ( e , index ) : itemKeyboardNavigator . handleKeyUpEvent ( e , index ) ; } } } ; } type Selection = string | RangeSelection ; function isIndividualSelection ( selection ) { return typeof ( selection ) === "string" ; } class ItemHandler implements SingleSelectionHandler < string > { private _node : TreeModelNode | undefined ; constructor ( node ) { this . _node = node ; } public preselect ( ) { } public select ( ) { } public deselect ( ) { } public isSelected ( ) { return ! ! this . _node ?. isSelected ; } public item ( ) { return this . _node ?. id ?? "" ; } }