UNPKG

@rxap/tree

Version:

This package provides a tree component and data source for Angular applications. It includes features such as searching, filtering, and displaying hierarchical data. The package also offers directives for customizing the content of tree nodes.

1 lines 68.1 kB
{"version":3,"file":"rxap-tree.mjs","sources":["../../../../../packages/angular/tree/src/lib/search.form.ts","../../../../../packages/angular/tree/src/lib/tokens.ts","../../../../../packages/angular/tree/src/lib/tree-content.directive.ts","../../../../../packages/angular/tree/src/lib/tree.data-source.ts","../../../../../packages/angular/tree/src/lib/tree.component.ts","../../../../../packages/angular/tree/src/lib/tree.component.html","../../../../../packages/angular/tree/src/index.ts","../../../../../packages/angular/tree/src/rxap-tree.ts"],"sourcesContent":["import {\n Injectable,\n INJECTOR,\n Injector,\n Optional,\n Provider,\n SkipSelf,\n} from '@angular/core';\nimport {\n FormType,\n RXAP_FORM_DEFINITION,\n RXAP_FORM_INITIAL_STATE,\n RxapForm,\n RxapFormBuilder,\n RxapFormControl,\n RxapFormGroup,\n UseFormControl,\n} from '@rxap/forms';\n\nexport interface ISearchForm<Scope = unknown> {\n search: string;\n scope: Record<string, Scope[]>;\n}\n\n@RxapForm({\n id: 'search',\n autoSubmit: 500,\n})\n@Injectable()\nexport class SearchForm<Scope = unknown> implements FormType<ISearchForm<Scope>> {\n\n rxapFormGroup!: RxapFormGroup<ISearchForm<Scope>>;\n\n @UseFormControl()\n search!: RxapFormControl<string>;\n\n @UseFormControl()\n scope!: RxapFormControl<ISearchForm<Scope>['scope']>;\n\n}\n\nexport function FormFactory(\n injector: Injector,\n state: ISearchForm | null,\n existingFormDefinition: SearchForm | null,\n): SearchForm {\n if (existingFormDefinition) {\n return existingFormDefinition;\n }\n return new RxapFormBuilder<ISearchForm>(\n SearchForm,\n injector,\n ).build(state ?? {});\n}\n\nexport const SearchFormProviders: Provider[] = [\n SearchForm,\n {\n provide: RXAP_FORM_DEFINITION,\n useFactory: FormFactory,\n deps: [\n INJECTOR,\n [ new Optional(), RXAP_FORM_INITIAL_STATE ],\n [ new SkipSelf(), new Optional(), RXAP_FORM_DEFINITION ],\n ],\n },\n];\n","import { InjectionToken } from '@angular/core';\n\nexport const RXAP_TREE_CONTENT_EDITABLE_METHOD = new InjectionToken('rxap/tree-content-editable-method');\n","import {\n Directive,\n Inject,\n TemplateRef,\n} from '@angular/core';\n\n@Directive({\n selector: 'ng-template[rxapTreeContent]',\n standalone: true,\n})\nexport class TreeContentDirective {\n constructor(\n @Inject(TemplateRef)\n public readonly template: TemplateRef<any>,\n ) {\n }\n}\n","import {\n SelectionChange,\n SelectionModel,\n} from '@angular/cdk/collections';\nimport { FlatTreeControl } from '@angular/cdk/tree';\nimport {\n Inject,\n Injectable,\n InjectionToken,\n isDevMode,\n OnInit,\n Optional,\n} from '@angular/core';\nimport {\n BaseDataSource,\n BaseDataSourceMetadata,\n BaseDataSourceViewer,\n RXAP_DATA_SOURCE_METADATA,\n RxapDataSource,\n} from '@rxap/data-source';\nimport {\n EventOptions,\n ExpandNodeFunction,\n Node,\n NodeGetIconFunction,\n NodeGetStyleFunction,\n NodeGetTypeFunction,\n NodeHasDetailsFunction,\n NodeToDisplayFunction,\n} from '@rxap/data-structure-tree';\nimport { Method } from '@rxap/pattern';\nimport { ToggleSubject } from '@rxap/rxjs';\nimport {\n coerceArray,\n getIdentifierPropertyValue,\n joinPath,\n Required,\n WithChildren,\n WithIdentifier,\n} from '@rxap/utilities';\nimport {\n BehaviorSubject,\n combineLatest,\n debounceTime,\n from,\n merge,\n Observable,\n Subject,\n Subscription,\n} from 'rxjs';\nimport {\n map,\n startWith,\n switchMap,\n tap,\n} from 'rxjs/operators';\nimport {\n ISearchForm,\n SearchForm,\n} from './search.form';\n\nexport function isSelectionChange<T>(obj: any): obj is SelectionChange<T> {\n return !!obj && obj['added'] !== undefined && obj['removed'] !== undefined;\n}\n\nexport interface TreeDataSourceMetadata extends BaseDataSourceMetadata {\n selectMultiple?: boolean;\n expandMultiple?: boolean;\n scopeTypes?: string[];\n /**\n * If true the tree will be refreshed with caching disbabled after the first load\n */\n autoRefreshWithoutCache?: boolean;\n}\n\nexport const RXAP_TREE_DATA_SOURCE_ROOT_REMOTE_METHOD = new InjectionToken(\n 'rxap/tree/data-source/root-remote-method',\n);\nexport const RXAP_TREE_DATA_SOURCE_CHILDREN_REMOTE_METHOD = new InjectionToken(\n 'rxap/tree/data-source/children-remote-method',\n);\n\nexport const RXAP_TREE_DATA_SOURCE_APPLY_FILTER_METHOD = new InjectionToken(\n 'rxap/tree/data-source/apply-filter-method');\n\nexport function flatTree<Data extends WithIdentifier & WithChildren = any>(\n tree: Node<Data> | Array<Node<Data>>,\n all = false,\n): Array<Node<Data>> {\n tree = coerceArray(tree);\n\n function flat(acc: any[], list: any[]) {\n return [ ...acc, ...list ];\n }\n\n const _flatTree = (node: Node<Data>): Array<Node<Data>> => {\n if (!Array.isArray(node.children)) {\n if (isDevMode()) {\n console.log(node);\n }\n throw new Error('Node has not defined children');\n }\n\n if (all || node.expanded) {\n return [\n node,\n ...node.children.map((child) => _flatTree(child)).reduce(flat, []),\n ];\n } else {\n return [ node ];\n }\n };\n\n return tree\n .map((child) => _flatTree(child))\n .reduce((acc, items) => [ ...acc, ...items ], []);\n}\n\nexport interface TreeApplyFilterParameter<Form extends ISearchForm = ISearchForm, Data extends WithIdentifier & WithChildren = any> {\n tree: Array<Node<Data>>,\n filter: Form;\n scopeTypes?: string[]\n}\n\n@Injectable()\nexport class DefaultTreeApplyFilterMethod<Data extends WithIdentifier & WithChildren = any>\n implements Method<Array<Node<Data>>, TreeApplyFilterParameter> {\n\n protected lastFilter: ISearchForm | null = null;\n\n call(\n {tree, filter, scopeTypes}: TreeApplyFilterParameter,\n ): Array<Node<Data>> | Promise<Array<Node<Data>>> {\n\n const nodes = flatTree(tree, true);\n\n // if (this.isEqualToLastFilter(filter)) {\n // return flatTree(tree, false).filter(node => node.isVisible);\n // }\n\n const hasScopeFilter = (\n filter.scope &&\n Object.keys(filter.scope) &&\n Object.values(filter.scope).some((list) => list.length > 0)\n );\n\n // if not scope and the search filter is an empty string, collapse all non-root nodes\n if (!filter.search && filter.search !== this.lastFilter?.search) {\n nodes\n .filter(node => node.parent)\n .forEach(node => node.collapse({quite: true}));\n }\n\n if (filter.search || hasScopeFilter) {\n\n nodes.forEach(node => node.hide());\n\n for (const [ type, list ] of Object.entries(filter.scope ?? {})) {\n for (const node of nodes) {\n if (node.type === type) {\n if (list.some(item => getIdentifierPropertyValue(item) === node.id)) {\n node.show({forEachChildren: true});\n }\n }\n }\n }\n\n if (filter.search) {\n for (const node of nodes) {\n if (hasScopeFilter && node.hidden) {\n // if the filter has a scope filter. Only check the search filter on nodes that are shown by the scope\n // filter\n continue;\n }\n const display = node.display?.toLowerCase();\n if (display) {\n if (display.includes(filter.search.toLowerCase())) {\n node.show({parents: true});\n } else {\n node.hide();\n }\n } else {\n // if node has no display, it is not shown\n node.hide();\n }\n }\n // hide each node that has no visible children\n // there exists the edge case that a node has visible children, but the node.hide() function is called\n // after the child.show({ parent:true })\n for (const node of nodes.filter(n => n.hidden && n.hasChildren && n.children.some(child => child.isVisible))) {\n if (node.hidden) {\n console.warn('Edge case detected. Node has visible children but is hidden.');\n node.show();\n }\n }\n // expand each node that has visible children\n for (const node of nodes.filter(n => n.hasChildren)) {\n if (node.children.some(child => child.isVisible)) {\n // set quite to true to prevent the tree from reloading -> this would result in a infinite loop\n node.expand({quite: true});\n }\n }\n }\n\n } else {\n nodes.forEach(node => node.show());\n }\n\n this.lastFilter = filter;\n\n return flatTree(tree, false).filter(node => node.isVisible);\n\n }\n\n protected isEqualToLastFilter(filter: ISearchForm): boolean {\n if (this.lastFilter) {\n if (this.lastFilter.search === filter.search) {\n if (this.lastFilter.scope && filter.scope) {\n if (Object.keys(this.lastFilter.scope).every(key => Object.keys(filter.scope).includes(key))) {\n if (Object.entries(this.lastFilter.scope)\n .every(([ key, scope ]) => scope.some(item => filter.scope[key].includes(item)))) {\n return true;\n }\n }\n }\n if (filter.scope === this.lastFilter.scope) {\n return true;\n }\n }\n }\n return false;\n }\n\n}\n\n@RxapDataSource('tree')\n@Injectable()\nexport class TreeDataSource<\n Data extends WithIdentifier & WithChildren = any,\n RootParameters = any,\n NodeParameters = any,\n> extends BaseDataSource<Array<Node<Data>>, TreeDataSourceMetadata> implements OnInit {\n public tree$ = new BehaviorSubject<Array<Node<Data>>>([]);\n @Required public treeControl!: FlatTreeControl<Node<Data>>;\n public selected!: SelectionModel<Node<Data>>;\n public expanded!: SelectionModel<string>;\n // TODO : änlich problem wie bei der redundaten expand SelectionModel.\n public override loading$ = new ToggleSubject(true);\n public searchForm: SearchForm | null = null;\n protected override _data$ = new BehaviorSubject<Array<Node<Data>>>([]);\n private _expandedLocalStorageSubscription: Subscription | null = null;\n private _selectedLocalStorageSubscription: Subscription | null = null;\n // im localStorage wird nur die id gespeichert.\n private _preSelected: string[] = [];\n private _refreshMatchFilter = new Subject<void>();\n private readonly applyFilterMethod: Method<Array<Node<Data>>, TreeApplyFilterParameter>;\n\n constructor(\n @Inject(RXAP_TREE_DATA_SOURCE_ROOT_REMOTE_METHOD)\n public readonly rootRemoteMethod: Method<Data | Data[], RootParameters>,\n @Optional()\n @Inject(RXAP_TREE_DATA_SOURCE_CHILDREN_REMOTE_METHOD)\n public readonly childrenRemoteMethod: Method<Data[], Node<Data>> | null = null,\n @Optional()\n @Inject(RXAP_TREE_DATA_SOURCE_APPLY_FILTER_METHOD)\n applyFilterMethod: Method<Array<Node<Data>>, TreeApplyFilterParameter> | null = null,\n @Optional()\n @Inject(RXAP_DATA_SOURCE_METADATA)\n metadata: TreeDataSourceMetadata | null = null,\n ) {\n super(metadata);\n this.applyFilterMethod = applyFilterMethod ?? new DefaultTreeApplyFilterMethod();\n // TODO add new SelectModel class that saves the select model to the localStorage\n this.initSelected();\n this.initExpanded();\n }\n\n private _nodeParameters: NodeParameters | null = null;\n\n public get nodeParameters(): NodeParameters | null {\n return this._nodeParameters;\n }\n\n public set nodeParameters(nodeParameters: NodeParameters | null) {\n this._nodeParameters = nodeParameters;\n this.tree$.value.forEach(node => node.parameters = nodeParameters);\n }\n\n ngOnInit() {\n if (this.searchForm) {\n combineLatest([\n this.tree$,\n (this.searchForm.rxapFormGroup.value$ as Observable<any>).pipe(debounceTime(1000)),\n ]).pipe(\n switchMap(async ([ tree, filter ]) => await this.applyFilterMethod.call({\n tree,\n filter,\n scopeTypes: this.metadata?.scopeTypes,\n })),\n map(nodes => coerceArray(nodes)),\n )\n .subscribe(data => this._data$.next(data));\n } else {\n this.tree$.pipe(\n map(tree => flatTree(tree).filter(node => node.isVisible)),\n tap(nodes => nodes.forEach(node => node.show())),\n ).subscribe(data => this._data$.next(data));\n }\n }\n\n public toDisplay: NodeToDisplayFunction<Data> = () =>\n 'to display function not defined';\n\n public getIcon: NodeGetIconFunction<Data> = () => null;\n\n public getType: NodeGetTypeFunction<Data> = () => null;\n\n public getStyle: NodeGetStyleFunction<Data> = () => (\n {}\n );\n\n public hasDetails: NodeHasDetailsFunction<Data> = () => true;\n\n public matchFilter: (node: Node<Data>) => boolean = () => true;\n\n public async getTreeRoot(options: { cache?: boolean } = {}): Promise<Array<Node<Data>>> {\n this.loading$.enable();\n const root: Data | Data[] = await this.getRoot(options);\n\n let rootNodes: Array<Node<Data>>;\n\n if (Array.isArray(root)) {\n rootNodes = await Promise.all(root.map((node) => this.toNode(null, node)));\n } else {\n rootNodes = [ await this.toNode(null, root) ];\n }\n\n const tmpSelectedNodes: Node<Data>[] = [];\n\n const restoreExpandAndSelectedState = (node: Node<Data>) => {\n if (this.expanded.isSelected(node.id)) {\n (node as any)._expanded = true;\n }\n if (this.selected.selected.some(n => n.id === node.id)) {\n console.log('restore selected', node.display);\n (node as any)._selected = true;\n tmpSelectedNodes.push(node);\n }\n node.children.forEach(restoreExpandAndSelectedState);\n };\n\n rootNodes.forEach(restoreExpandAndSelectedState);\n\n console.log('restore expand and selected state', tmpSelectedNodes);\n this.selected.setSelection(...tmpSelectedNodes);\n\n this.tree$.next(rootNodes);\n\n this.loading$.disable();\n\n return rootNodes;\n }\n\n public selectNode(node: Node<Data>): Promise<void> {\n if (!this.selected.isMultipleSelection()) {\n if (this.selected.hasValue()) {\n const currentSelectedNode = this.selected.selected[0];\n currentSelectedNode.deselect();\n }\n }\n this.selected.select(node);\n node.parent?.expand();\n return Promise.resolve();\n }\n\n public setTreeControl(treeControl: FlatTreeControl<Node<Data>>): void {\n this.treeControl = treeControl;\n }\n\n public setMatchFilter(matchFilter: (node: Node<Data>) => boolean): void {\n this.matchFilter = matchFilter;\n }\n\n public setToDisplay(\n toDisplay: NodeToDisplayFunction<Data> = this.toDisplay,\n ): void {\n this.toDisplay = toDisplay;\n }\n\n public setGetIcon(getIcon: NodeGetIconFunction<Data> = this.getIcon): void {\n this.getIcon = getIcon;\n }\n\n public setHasDetails(\n hasDetails: NodeHasDetailsFunction<Data> = this.hasDetails,\n ): void {\n this.hasDetails = hasDetails;\n }\n\n public deselectNode(node: Node<Data>): Promise<void> {\n this.selected.deselect(node);\n return Promise.resolve();\n }\n\n public async expandNode(node: Node<Data>, options?: EventOptions): Promise<void> {\n if (node.parent && !node.parent.expanded) {\n console.log('expand parent', node.parent.display);\n node.parent?.expand({quite: true});\n }\n if (!options?.onlySelf) {\n // required to sync the expanstion state with the tree control\n // if the collpase is trigged by node.expand this state is not\n // sync with the tree control\n this.treeControl.expansionModel.select(node);\n }\n\n if (node.item.hasChildren && !node.item.children?.length) {\n node.isLoading$.enable();\n\n const children = await this.getChildren(node);\n\n // add the loaded children to the item object\n node.item.children = children;\n\n node.addChildren(\n await Promise.all(children.map((child) =>\n this.toNode(node, child, node.depth + 1, node.onExpand, node.onCollapse),\n )),\n );\n\n node.isLoading$.disable();\n }\n\n console.log('expand node', node.display);\n this.expanded.select(node.id);\n\n // node.parent?.expand({quite: true});\n\n if (!options?.quite) {\n this.tree$.next(this.tree$.value);\n }\n\n }\n\n public async getChildren(node: Node<Data>): Promise<Data[]> {\n if (!this.childrenRemoteMethod) {\n throw new Error(\n `The node '${ node.id }' has unloaded children but the RXAP_TREE_DATA_SOURCE_CHILDREN_REMOTE_METHOD is not provided.`);\n }\n return this.childrenRemoteMethod.call(node);\n }\n\n public async getRoot(options: { cache?: boolean } = {}): Promise<Data | Data[]> {\n const rootParameters = await this.getRootParameters(options);\n return this.rootRemoteMethod.call(rootParameters);\n }\n\n public async getRootParameters(options: { cache?: boolean } = {}): Promise<RootParameters> {\n return options as any;\n }\n\n // TODO : find better solution to allow the overwrite of the toNode method\n // without losing the custom preselect and preexpand function\n\n public getNodeById(id: string): Node<Data> | null {\n function getNodeById(node: Node<Data>, nodeId: string) {\n if (node.id === nodeId) {\n return node;\n }\n if (node.hasNode(nodeId)) {\n return node.getNode(nodeId);\n } else {\n return null;\n }\n }\n\n return (\n this.tree$.value\n .map((node) => getNodeById(node, id))\n .filter(Boolean)[0] || null\n );\n }\n\n public async toNode(\n parent: Node<Data> | null,\n item: Data,\n depth = 0,\n onExpand: ExpandNodeFunction<Data> = this.expandNode.bind(this),\n onCollapse: ExpandNodeFunction<Data> = this.collapseNode.bind(this),\n onSelect: ExpandNodeFunction<Data> = this.selectNode.bind(this),\n onDeselect: ExpandNodeFunction<Data> = this.deselectNode.bind(this),\n ): Promise<Node<Data>> {\n return Node.ToNode(\n parent,\n item,\n depth,\n onExpand,\n onCollapse,\n this.toDisplay,\n this.getIcon,\n this.getType,\n onSelect,\n onDeselect,\n this.hasDetails,\n this.getStyle,\n this.nodeParameters,\n );\n }\n\n public collapseNode(node: Node<Data>, options?: EventOptions): Promise<void> {\n\n if (!options?.onlySelf) {\n // required to sync the expanstion state with the tree control\n // if the collpase is trigged by node.colapse this state is not\n // sync with the tree control\n this.treeControl.expansionModel.deselect(node);\n }\n\n this.expanded.deselect(node.id);\n\n if (!options?.quite) {\n this.tree$.next(this.tree$.value);\n }\n\n return Promise.resolve();\n }\n\n /**\n * Converts the tree structure into a list.\n *\n * @param tree\n * @param all true - include nodes children that are not expanded\n */\n public flatTree(tree: Node<Data>, all = false): Array<Node<Data>> {\n return flatTree(tree, all);\n }\n\n public override destroy(): void {\n super.destroy();\n // TODO : add subscription handler to BaseDataSource\n if (this._expandedLocalStorageSubscription) {\n this._expandedLocalStorageSubscription.unsubscribe();\n }\n if (this._selectedLocalStorageSubscription) {\n this._selectedLocalStorageSubscription.unsubscribe();\n }\n }\n\n public refreshMatchFilter(): void {\n this._refreshMatchFilter.next();\n }\n\n public override async refresh(): Promise<any> {\n console.log('selected', this.selected.selected.map((node) => node.id));\n console.log('expanded', this.expanded.selected.slice());\n await this.getTreeRoot({cache: false});\n\n // refresh all expanded nodes;\n\n // const loadExpandedNodes = async (children: ReadonlyArray<Node<Data>>) => {\n // for (const child of children) {\n // if (this.expanded.isSelected(child.id)) {\n // // call the node method to ensure that the expanded property of\n // // Node is set.\n // await child.expand();\n // }\n //\n // if (child.hasChildren) {\n // await loadExpandedNodes(child.children);\n // }\n // }\n // };\n //\n // await Promise.all(\n // rootNodes\n // .filter((node) => node.hasChildren)\n // .map((node) => loadExpandedNodes(node.children)),\n // );\n\n // console.log('selected', this.selected.selected.map((node) => node.id));\n //\n // const preSelected = this.selected.selected.slice();\n // this.selected.clear();\n // preSelected.forEach(node => {\n // node?.select();\n // });\n\n // const selected: Array<Node<Data>> = this.selected.selected\n // .map((node) => this.getNodeById(node.id))\n // .filter(Boolean) as any;\n // this.selected.clear();\n // this.selected.select(...selected);\n }\n\n public override reset(): any {\n this.selected.clear();\n this.expanded.clear();\n return this.getTreeRoot();\n }\n\n public setGetStyle(getStyle: NodeGetStyleFunction<any> = this.getStyle) {\n this.getStyle = getStyle;\n }\n\n public setGetType(getType: NodeGetTypeFunction<any> = this.getType) {\n this.getType = getType;\n }\n\n /**\n * recall the getStyle, getIcon and toDisplay methods\n * and update the node objects\n */\n public updateNodes() {\n this._data$.value.forEach(node => {\n node.style = this.getStyle(node.item);\n node.icon = coerceArray(this.getIcon(node.item));\n node.display = this.toDisplay(node.item);\n });\n this._data$.next(this._data$.value);\n }\n\n protected override _connect(\n collectionViewer: Required<BaseDataSourceViewer>,\n ): Observable<Array<Node<Data>>> {\n this.init();\n this.treeControl.expansionModel.changed.pipe(\n tap(async change => {\n if (isSelectionChange<Node<Data>>(change)) {\n const promiseList: Array<Promise<any>> = [];\n if (change.added) {\n promiseList.push(...change.added.map((node) => node.expand({onlySelf: true, quite: true})));\n }\n if (change.removed) {\n promiseList.push(...change.removed\n .slice()\n .reverse()\n .map((node) => node.collapse({onlySelf: true, quite: true})));\n }\n await Promise.all(promiseList);\n this.tree$.next(this.tree$.value);\n }\n }),\n ).subscribe();\n\n let loadRoot: Promise<Array<Node<Data>> | void> = Promise.resolve();\n\n if (this.tree$.value.length === 0) {\n loadRoot = this.getTreeRoot();\n }\n\n let autoRefreshExecuted = false;\n\n return from(loadRoot).pipe(\n // tap((rootNodes) => {\n // if (rootNodes) {\n // const promises: Promise<any>[] = [];\n // if (this.metadata.selectMultiple) {\n // // if (!this.selected.hasValue()) {\n // // promises.push(\n // // ...rootNodes\n // // .filter((node) => node.hasDetails)\n // // .map((node) => node.select()),\n // // );\n // // }\n // if (!this.expanded.hasValue()) {\n // promises.push(\n // ...rootNodes\n // .filter((node) => node.hasChildren)\n // .map((node) => node.expand()),\n // );\n // }\n // } else if (rootNodes.length) {\n // const rootNode = rootNodes[0];\n // // if (!this.selected.hasValue()) {\n // // // TODO : rename hasDetails to isSelectable\n // // if (rootNode.hasDetails) {\n // // promises.push(rootNode.select());\n // // }\n // // }\n // if (!this.expanded.hasValue()) {\n // promises.push(rootNode.expand());\n // }\n // }\n // return Promise.all(promises);\n // }\n // return Promise.resolve();\n // }),\n switchMap(() =>\n merge(collectionViewer.viewChange, this._data$).pipe(\n map(() => this._data$.value),\n ),\n ),\n switchMap(nodeList => this._refreshMatchFilter.pipe(\n startWith(null),\n map(() => nodeList.filter(node => this.matchFilter(node))),\n )),\n tap(() => {\n if (this._preSelected.length) {\n console.log('restore selected', this._preSelected);\n this.selected.clear();\n const nodes = this._preSelected.map((id) => this.getNodeById(id));\n this._preSelected = [];\n nodes.forEach(node => {\n node?.select();\n });\n }\n }),\n tap(() => {\n if (this.metadata.autoRefreshWithoutCache) {\n if (!autoRefreshExecuted) {\n autoRefreshExecuted = true;\n console.log('auto refresh');\n this.refresh();\n }\n }\n }),\n );\n }\n\n private initSelected(): void {\n const key = joinPath('rxap/tree', this.id, 'selected');\n if (this.metadata['cacheSelected']) {\n if (localStorage.getItem(key)) {\n try {\n this._preSelected = JSON.parse(localStorage.getItem(key)!);\n } catch (e: any) {\n console.error('parse expanded tree data source nodes failed');\n }\n }\n }\n\n this.selected = new SelectionModel<Node<Data>>(\n !!this.metadata.selectMultiple,\n [],\n );\n if (this.metadata['cacheSelected']) {\n this._selectedLocalStorageSubscription = this.selected.changed\n .pipe(\n tap(() =>\n localStorage.setItem(\n key,\n JSON.stringify(this.selected.selected.map((s) => s.id)),\n ),\n ),\n )\n .subscribe();\n }\n }\n\n private initExpanded(): void {\n const key = joinPath('rxap/tree', this.id, 'expanded');\n let expanded = [];\n if (this.metadata['cacheExpanded']) {\n if (localStorage.getItem(key)) {\n try {\n expanded = JSON.parse(localStorage.getItem(key)!);\n } catch (e: any) {\n console.error('parse expanded tree data source nodes failed');\n }\n }\n }\n\n this.expanded = new SelectionModel<string>(\n this.metadata.expandMultiple !== false,\n expanded,\n );\n if (this.metadata['cacheExpanded']) {\n this._expandedLocalStorageSubscription = this.expanded.changed\n .pipe(\n tap(() =>\n localStorage.setItem(\n key,\n JSON.stringify(this.expanded.selected),\n ),\n ),\n )\n .subscribe();\n }\n }\n\n}\n","import {\n PortalModule,\n TemplatePortal,\n} from '@angular/cdk/portal';\nimport { FlatTreeControl } from '@angular/cdk/tree';\nimport {\n AsyncPipe,\n NgClass,\n NgForOf,\n NgIf,\n NgStyle,\n} from '@angular/common';\nimport {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ContentChild,\n ElementRef,\n EventEmitter,\n HostListener,\n Inject,\n Input,\n isDevMode,\n OnInit,\n Optional,\n Output,\n Renderer2,\n signal,\n ViewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { MatDividerModule } from '@angular/material/divider';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatTreeModule } from '@angular/material/tree';\nimport { ContenteditableDirective } from '@rxap/contenteditable';\nimport {\n Node,\n NodeGetIconFunction,\n NodeGetStyleFunction,\n NodeGetTypeFunction,\n NodeHasDetailsFunction,\n NodeToDisplayFunction,\n} from '@rxap/data-structure-tree';\nimport { RXAP_FORM_DEFINITION } from '@rxap/forms';\nimport { IconDirective } from '@rxap/material-directives/icon';\nimport { Method } from '@rxap/pattern';\nimport {\n DebounceCall,\n WithChildren,\n WithIdentifier,\n} from '@rxap/utilities';\nimport {\n map,\n startWith,\n tap,\n} from 'rxjs/operators';\nimport { SearchForm } from './search.form';\nimport { RXAP_TREE_CONTENT_EDITABLE_METHOD } from './tokens';\nimport { TreeContentDirective } from './tree-content.directive';\nimport { TreeDataSource } from './tree.data-source';\n\n@Component({\n selector: 'rxap-tree',\n templateUrl: './tree.component.html',\n styleUrls: ['./tree.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n NgStyle,\n NgIf,\n MatProgressBarModule,\n MatTreeModule,\n MatIconModule,\n IconDirective,\n MatCheckboxModule,\n MatButtonModule,\n ContenteditableDirective,\n MatProgressSpinnerModule,\n MatDividerModule,\n PortalModule,\n AsyncPipe,\n NgClass,\n NgForOf,\n ]\n})\nexport class TreeComponent<Data extends WithIdentifier & WithChildren = any>\n implements OnInit, AfterContentInit {\n public treeControl: FlatTreeControl<Node<Data>>;\n @Input({required: true})\n public dataSource!: TreeDataSource<Data>;\n @Input()\n public contentEditableMethod?: Method<any, string | null> | null;\n @Input()\n public toDisplay?: NodeToDisplayFunction<any>;\n @Input()\n public getIcon?: NodeGetIconFunction<any>;\n @Input()\n public getType?: NodeGetTypeFunction<any>;\n @Input()\n public getStyle?: NodeGetStyleFunction<any>;\n @Input()\n public multiple = false;\n @Input()\n public hasDetails?: NodeHasDetailsFunction<any>;\n @ContentChild(TreeContentDirective, {static: true})\n public content?: TreeContentDirective;\n @Input()\n public hideLeafIcon = false;\n @Input()\n public id?: string;\n @Output()\n public details = new EventEmitter();\n @Input()\n public dividerOffset = '256px';\n public portal: TemplatePortal | null = null;\n @ViewChild('treeContainer', {static: true})\n public treeContainer!: ElementRef;\n\n public readonly showTreeNavigation = signal(true);\n\n /**\n * Indicates that the divider is moved with mouse down\n * @private\n */\n private _moveDivider = false;\n /**\n * Holds the current tree container width.\n * If null the move divider feature was not yet used and the initial\n * container width is not calculated\n * @private\n */\n private _treeContainerWidth: number | null = null;\n\n constructor(\n @Inject(ViewContainerRef)\n private readonly viewContainerRef: ViewContainerRef,\n @Inject(ChangeDetectorRef)\n private readonly cdr: ChangeDetectorRef,\n @Optional()\n @Inject(RXAP_TREE_CONTENT_EDITABLE_METHOD)\n contentEditableMethod: Method<any, string | null> | null,\n private readonly renderer: Renderer2,\n private readonly elementRef: ElementRef<HTMLElement>,\n @Optional()\n @Inject(RXAP_FORM_DEFINITION)\n public readonly searchForm: SearchForm | null,\n ) {\n this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable);\n this.contentEditableMethod = contentEditableMethod;\n }\n\n public get nodeDisplayEditable(): boolean {\n return !!this.contentEditableMethod;\n }\n\n public get cacheId() {\n return [ 'rxap', 'tree', this.id ].join('/');\n }\n\n public getLevel = (node: Node<Data>) => node.depth;\n\n public isExpandable = (node: Node<Data>) => node.hasChildren;\n\n public hasChild = (_: number, nodeData: Node<Data>) => nodeData.hasChildren;\n\n public ngOnInit() {\n this.dataSource.searchForm = this.searchForm;\n this.dataSource.setTreeControl(this.treeControl);\n this.dataSource.setToDisplay(this.toDisplay);\n this.dataSource.setGetIcon(this.getIcon);\n this.dataSource.setHasDetails(this.hasDetails);\n this.dataSource.setGetStyle(this.getStyle);\n this.dataSource.setGetType(this.getType);\n this.multiple = this.dataSource.metadata.selectMultiple ?? this.multiple;\n\n if (this.dataSource.selected.hasValue()) {\n this.dataSource.selected.selected.forEach((node) =>\n this.openDetails(node),\n );\n }\n\n const cachedOffset = localStorage.getItem(this.cacheId);\n if (cachedOffset && cachedOffset.match(/^(\\d+\\.)?\\d+px$/)) {\n this.setDividerOffset(cachedOffset);\n } else if (isDevMode()) {\n console.log('Divider offset cache is not available or invalid: ' + cachedOffset);\n }\n }\n\n public ngAfterContentInit(): void {\n this.dataSource.selected.changed\n .pipe(\n map(($event) => $event.source.selected),\n startWith(this.dataSource.selected.selected),\n tap((selected) => selected.forEach((node) => this.openDetails(node))),\n )\n .subscribe();\n }\n\n @DebounceCall(100)\n public openDetails(node: Node<Data>): void {\n if (this.content) {\n this.portal = new TemplatePortal<any>(\n this.content.template,\n this.viewContainerRef,\n {\n $implicit: node.item,\n node,\n },\n );\n this.cdr.markForCheck();\n }\n this.details.emit(node.item);\n }\n\n public onContentEditableChange(value: string | null, node: Node<Data>) {\n return this.contentEditableMethod?.call(value, node.item, node);\n }\n\n\n onMousedown() {\n this._moveDivider = true;\n }\n\n @HostListener('mouseup')\n onMouseup() {\n this._moveDivider = false;\n }\n\n @HostListener('mousemove', [ '$event' ])\n onMousemove($event: MouseEvent) {\n if (this._moveDivider) {\n if (!this._treeContainerWidth) {\n this._treeContainerWidth = this.treeContainer.nativeElement.clientWidth as number;\n }\n const rect = this.elementRef.nativeElement.getBoundingClientRect();\n this._treeContainerWidth = Math.min(Math.max($event.clientX - (\n rect.left + 12\n ), 128), rect.right - rect.left - 128);\n const offset = this._treeContainerWidth + 'px';\n this.setDividerOffset(offset);\n }\n }\n\n toggleTreeNavigation() {\n this.showTreeNavigation.update((value) => !value);\n }\n\n private setDividerOffset(offset: string) {\n this.dividerOffset = offset;\n this.renderer.setStyle(this.treeContainer.nativeElement, 'max-width', offset);\n this.renderer.setStyle(this.treeContainer.nativeElement, 'min-width', offset);\n this.renderer.setStyle(this.treeContainer.nativeElement, 'flex-basis', offset);\n localStorage.setItem(this.cacheId, offset);\n }\n}\n","<div class=\"grow flex flex-col gap-2 justify-start items-start\">\n <button (click)=\"toggleTreeNavigation()\" class=\"!justify-start\" mat-button type=\"button\">\n <span class=\"flex flex-row justify-start items-center gap-6\">\n <ng-template [ngIf]=\"showTreeNavigation()\">\n <mat-icon>arrow_back</mat-icon>\n <span i18n>Hide tree navigation</span>\n </ng-template>\n <ng-template [ngIf]=\"!showTreeNavigation()\">\n <mat-icon>arrow_forward</mat-icon>\n <span i18n>Show tree navigation</span>\n </ng-template>\n </span>\n </button>\n <div class=\"flex flex-row grow w-full\">\n <div #treeContainer [ngClass]=\"{ 'hidden': !showTreeNavigation() }\"\n [ngStyle]=\"{ maxWidth: dividerOffset, minWidth: dividerOffset, flexBasis: dividerOffset }\"\n class=\"w-fit grow-0 overflow-y-auto\">\n <mat-progress-bar *ngIf=\"!dataSource || (dataSource.loading$ | async)\" mode=\"indeterminate\"></mat-progress-bar>\n <ng-content select=\"[searchHeader]\"></ng-content>\n <mat-tree *ngIf=\"dataSource; else loading\" [dataSource]=\"dataSource\" [treeControl]=\"treeControl\">\n\n <!-- Node without children -->\n <mat-tree-node *matTreeNodeDef=\"let node\" matTreeNodePadding>\n <div class=\"flex flex-row justify-start items-center gap-2\">\n <mat-icon [ngClass]=\"{ 'hidden': hideLeafIcon }\">subdirectory_arrow_right</mat-icon>\n <ng-container *ngIf=\"node.icon?.length\">\n <mat-icon *ngFor=\"let icon of node.icon\" [rxapIcon]=\"$any(icon)\"></mat-icon>\n </ng-container>\n <ng-template [ngIf]=\"multiple\">\n <mat-checkbox\n (change)=\"node.toggleSelect()\"\n [checked]=\"node.selected\"\n [disabled]=\"!node.hasDetails\"\n [ngStyle]=\"node.style\"\n class=\"grow-0 truncate\">\n {{ node.display }}\n </mat-checkbox>\n </ng-template>\n <ng-template [ngIf]=\"!multiple\">\n <button\n (click)=\"node.select()\"\n [color]=\"node.selected ? 'primary' : undefined\"\n [disabled]=\"!node.hasDetails\"\n [ngStyle]=\"node.style\"\n class=\"grow-0 truncate\"\n mat-button\n type=\"button\"\n >\n {{ node.display }}\n </button>\n </ng-template>\n </div>\n </mat-tree-node>\n\n <!-- Node with children -->\n <mat-tree-node *matTreeNodeDef=\"let node; when: hasChild\" matTreeNodePadding>\n <button [attr.aria-label]=\"'toggle ' + node.filename\" mat-icon-button matTreeNodeToggle type=\"button\">\n <mat-icon class=\"mat-icon-rtl-mirror\">\n {{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}\n </mat-icon>\n </button>\n <div class=\"flex flex-row justify-start items-center gap-2\">\n <ng-container *ngIf=\"node.icon?.length else withoutIcon\">\n <mat-icon *ngFor=\"let icon of node.icon\" [rxapIcon]=\"$any(icon)\"></mat-icon>\n </ng-container>\n <ng-template #withoutIcon>\n <div></div>\n </ng-template>\n <ng-template [ngIfElse]=\"withoutDetails\" [ngIf]=\"node.hasDetails\">\n <button\n (click)=\"node.select()\"\n [color]=\"node.selected ? 'primary' : undefined\"\n [ngStyle]=\"node.style\"\n class=\"grow-0 truncate\"\n mat-button\n type=\"button\"\n >\n {{ node.display }}\n </button>\n </ng-template>\n <ng-template #withoutDetails>\n <span\n (change)=\"onContentEditableChange($event, node)\"\n [disabled]=\"!nodeDisplayEditable\"\n [ngStyle]=\"node.style\"\n class=\"grow-0 truncate\"\n rxapContenteditable>\n {{ node.display }}\n </span>\n </ng-template>\n <mat-progress-spinner\n *ngIf=\"node.isLoading$ | async\"\n [diameter]=\"16\"\n class=\"grow-0 pl-4\"\n mode=\"indeterminate\"\n ></mat-progress-spinner>\n </div>\n </mat-tree-node>\n\n </mat-tree>\n </div>\n\n <div (mousedown)=\"onMousedown()\" *ngIf=\"showTreeNavigation()\"\n class=\"divider cursor-ew-resize px-3 grow-0\">\n <mat-divider [vertical]=\"true\" class=\"h-full\"></mat-divider>\n </div>\n\n <div class=\"grow\">\n <ng-container *ngIf=\"portal\">\n <ng-template [cdkPortalOutlet]=\"portal\"></ng-template>\n </ng-container>\n <ng-content></ng-content>\n </div>\n </div>\n</div>\n\n<ng-template #loading>Load data source</ng-template>\n","// region \nexport * from './lib/search.form';\nexport * from './lib/tokens';\nexport * from './lib/tree-content.directive';\nexport * from './lib/tree.component';\nexport * from './lib/tree.data-source';\n// endregion\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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