import { MdcComponent } from '@aurelia-mdc-web/base' ; import { MDCFoundation } from '@material/base' ; import { customElement , bindable , useView , PLATFORM , processContent , ViewCompiler , ViewResources , inject , Optional , Container , ViewFactory } from 'aurelia-framework' ; import { INode } from './i-node' ; let id = 0 ; const templateLookup = { } ; const getNextNodeTemplateId = ( ) => ++ id ; const NODE_SELECTED_EVENT = 'mdctree:node-selected' ; export class MDCTreeViewFoundation extends MDCFoundation { } @ inject ( Element , Container ) @ customElement ( 'mdc-tree-view' ) @ useView ( PLATFORM . moduleName ( './mdc-tree-view.html' ) ) @ processContent ( MdcTreeView . processContent ) export class MdcTreeView extends MdcComponent < MDCTreeViewFoundation > { getDefaultFoundation ( ) { return new MDCTreeViewFoundation ( ) ; } static processContent ( _viewCompiler , _resources , element ) { const treeNode = element . querySelector ( 'mdc-tree-node' ) ; if ( treeNode ) { const nodeTemplateId = getNextNodeTemplateId ( ) ; element . setAttribute ( 'data-template-id' , nodeTemplateId . toString ( ) ) ; templateLookup [ nodeTemplateId ] = treeNode . innerHTML ; } element . innerHTML = '' ; return false ; } param element param container private static getNodeFactory ( element , container ) { const parent = container . parent ? container . parent . get ( Optional . of ( MdcTreeView ) ) : null ; const isRoot = ! parent ; if ( isRoot ) { const nodeTemplateId = element . getAttribute ( 'data-template-id' ) ; if ( nodeTemplateId && templateLookup [ nodeTemplateId ] ) { const nodeTemplate = templateLookup [ nodeTemplateId ] ; const nodeViewFactory = container . get ( ViewCompiler ) . compile ( `<template>${ nodeTemplate }</template>` , container . get ( ViewResources ) ) ; return nodeViewFactory ; } else { return container . get ( ViewCompiler ) . compile ( '<template>${$node}</template>' , container . get ( ViewResources ) ) ; } } else { return parent . nodeViewFactory as ViewFactory ; } } constructor ( root , container ) { super ( root ) ; this . nodeViewFactory = MdcTreeView . getNodeFactory ( root , container ) ; } nodeViewFactory : ViewFactory ; selectedNode ? : INode ; @ bindable nodes : INode [ ] ; /**
     * Allows for filtering tree nodes
     */ @ bindable filter : ( n : INode ) => boolean = ( ) => true ; @ bindable rootBindingContext : Record < string , unknown > ; bind ( bindingContext ) { this . rootBindingContext = this . rootBindingContext ?? bindingContext ; } toggleExpanded ( n , e ) { n . expanded = ! n . expanded ; e . stopPropagation ( ) ; return false ; } nodeClicked ( n ? ) { if ( this . selectedNode ) { this . selectedNode . selected = false ; } if ( n ) { n . selected = true ; } this . selectedNode = n ; this . root . dispatchEvent ( new CustomEvent ( NODE_SELECTED_EVENT , { detail : { node : n } , bubbles : true } ) ) ; return true ; } childNodeSelected ( n ) { if ( this . selectedNode && this . selectedNode !== n ) { this . selectedNode . selected = false ; } this . selectedNode = n ; } findPath < T extends INode > ( nodes , predicate ) { const path = [ ] ; for ( let i = 0 ; i < nodes . length ; ++ i ) { if ( predicate ( nodes [ i ] ) ) { return [ i ] ; } if ( ! nodes [ i ] . children ) { continue ; } const filteredChildren = nodes [ i ] . children ! . filter ( x => this . filter ( x ) ) ; const childPath = this . findPath ( filteredChildren , predicate ) ; if ( childPath . length ) { return [ i , ... childPath ] ; } } return path ; } async expandPath ( path ) { const filteredNodes = this . nodes . filter ( x => this . filter ( x ) ) ; if ( path . length === 1 ) { this . nodeClicked ( filteredNodes [ path [ 0 ] ] ) ; this . root . querySelectorAll ( '.mdc-tree-view__node' ) [ path [ 0 ] ] . scrollIntoView ( ) ; } else { filteredNodes [ path [ 0 ] ] . expanded = true ; await this . initialised ; const childTreeView = await ( filteredNodes [ path [ 0 ] ] . childTreeViewPromise ) ; await childTreeView ?. expandPath ( path . slice ( 1 ) ) ; } } async find < T extends INode > ( predicate ) { const filteredNodes = this . nodes . filter ( x => this . filter ( x ) ) ; const path = this . findPath ( filteredNodes , predicate ) ; if ( path . length ) { await this . expandPath ( path ) ; } } dispatchEvent ( type , node ) { this . root . dispatchEvent ( new CustomEvent ( type , { bubbles : true , detail : { node } } ) ) ; } } hidden export interface IMdcTreeElement extends HTMLElement { au : { controller : { viewModel : MdcTreeView ; } ; } ; }