UNPKG

@bimeister/pupakit.tree

Version:
1 lines 99.8 kB
{"version":3,"file":"bimeister-pupakit.tree.mjs","sources":["../../src/declarations/constants/tree-node-offset.const.ts","../../src/components/tree-layout/components/tree-node-skeleton/tree-node-skeleton.component.ts","../../src/components/tree-layout/components/tree-node-skeleton/tree-node-skeleton.component.html","../../src/components/tree-layout/directives/tree-node-actions.directive.ts","../../src/components/tree-layout/components/tree-node/tree-node.component.ts","../../src/components/tree-layout/components/tree-node/tree-node.component.html","../../src/components/tree-layout/tree-layout.module.ts","../../src/declarations/classes/flat-tree-item.class.ts","../../src/declarations/classes/tree-ranged-data-source.class.ts","../../src/declarations/events/tree.events.ts","../../src/components/tree-new/directives/tree-item-template.directive.ts","../../src/components/tree-new/components/tree-new/tree-new.component.ts","../../src/components/tree-new/components/tree-new/tree-new.component.html","../../src/components/tree-new/tree-new.module.ts","../../src/tree.module.ts","../../src/declarations/constants/default-tree-item-size-px.const.ts","../../src/declarations/classes/tree-data-display-collection.class.ts","../../src/declarations/classes/default-tree-event-handler.class.ts","../../src/declarations/classes/tree-controller.class.ts","../../src/declarations/classes/tree-drag-and-drop.class.ts"],"sourcesContent":["export const TREE_NODE_OFFSET: number = 16;\n","import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';\nimport { TREE_NODE_OFFSET } from '../../../../declarations/constants/tree-node-offset.const';\n\n@Component({\n selector: 'pupa-tree-node-skeleton',\n templateUrl: './tree-node-skeleton.component.html',\n styleUrls: ['./tree-node-skeleton.component.scss'],\n encapsulation: ViewEncapsulation.Emulated,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TreeNodeSkeletonComponent {\n @Input() public level: number;\n\n public get offset(): string {\n return `${this.level * TREE_NODE_OFFSET}px`;\n }\n}\n","<div class=\"tree-node-skeleton\">\n <div [style.margin-left]=\"offset\" class=\"tree-node-skeleton__left\"></div>\n <div class=\"tree-node-skeleton__center\"></div>\n <div class=\"tree-node-skeleton__right\"></div>\n</div>\n","import { Directive, Input, OnChanges, TemplateRef } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\n\n@Directive({\n selector: '[pupaTreeNodeActions]',\n})\nexport class TreeNodeActionsDirective implements OnChanges {\n @Input() public pupaTreeNodeActions: boolean | string;\n\n public readonly isDisplayed$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);\n\n constructor(public readonly templateRef: TemplateRef<unknown>) {}\n\n public ngOnChanges(): void {\n this.isDisplayed$.next(Boolean(this.pupaTreeNodeActions));\n }\n}\n","import { ChangeDetectionStrategy, Component, ContentChild, Input, ViewEncapsulation } from '@angular/core';\nimport { TreeNodeActionsDirective } from '../../directives/tree-node-actions.directive';\nimport { TreeNodeProperties } from '../../../../declarations/interfaces/tree-node-properties.interface';\nimport { isNil } from '@bimeister/utilities';\nimport { TREE_NODE_OFFSET } from '../../../../declarations/constants/tree-node-offset.const';\n\n@Component({\n selector: 'pupa-tree-node',\n templateUrl: './tree-node.component.html',\n styleUrls: ['./tree-node.component.scss'],\n encapsulation: ViewEncapsulation.Emulated,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TreeNodeComponent {\n @Input() public treeNodeProperties: TreeNodeProperties;\n @ContentChild(TreeNodeActionsDirective) public readonly treeNodeActionsTemplate: TreeNodeActionsDirective;\n\n public get arrowIcon(): string {\n if (this.treeNodeProperties.isExpanded) {\n return 'app-chevron-down';\n }\n return 'app-chevron-right';\n }\n\n public get arrowOffset(): string {\n return `${this.treeNodeProperties.level * TREE_NODE_OFFSET}px`;\n }\n\n public get isSelected(): boolean {\n return this.treeNodeProperties.isSelected || this.treeNodeProperties.isHighlighted;\n }\n\n public expand(event: Event): void {\n event.stopPropagation();\n if (isNil(this.treeNodeProperties.expand)) {\n return;\n }\n this.treeNodeProperties.expand();\n }\n}\n","<div\n class=\"tree-node\"\n [class.tree-node--selected]=\"isSelected\"\n [class.tree-node--disabled]=\"treeNodeProperties.isDisabled\"\n [class.tree-node--expanded]=\"treeNodeProperties.isExpanded\"\n>\n <svg\n class=\"tree-node__selection\"\n width=\"2\"\n height=\"31\"\n viewBox=\"0 0 2 31\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M2 31V0C0.4 0 0 2 0 3V28C0 30.4 1.33333 31 2 31Z\" fill=\"currentColor\" />\n </svg>\n <div class=\"tree-node__left\">\n <div [style.margin-left]=\"arrowOffset\" class=\"tree-node__arrow-container\">\n <ng-container *ngIf=\"treeNodeProperties.isDirectory\">\n <pupa-button-icon\n (click)=\"expand($event)\"\n *ngIf=\"treeNodeProperties.hasChildren\"\n [icon]=\"arrowIcon\"\n [disabled]=\"treeNodeProperties.isDisabled\"\n [loading]=\"treeNodeProperties.isLoading\"\n size=\"s\"\n kind=\"secondary\"\n ></pupa-button-icon>\n <pupa-icon\n *ngIf=\"!treeNodeProperties.hasChildren\"\n class=\"tree-node__without-children\"\n name=\"app-circle\"\n ></pupa-icon>\n </ng-container>\n </div>\n <div class=\"tree-node__text\">\n <ng-content></ng-content>\n </div>\n </div>\n <div class=\"tree-node__right\">\n <div\n *ngIf=\"treeNodeActionsTemplate?.isDisplayed$ | async\"\n (click)=\"$event.stopPropagation()\"\n class=\"tree-node__right-container\"\n >\n <ng-container [ngTemplateOutlet]=\"treeNodeActionsTemplate?.templateRef\"></ng-container>\n </div>\n </div>\n</div>\n","import { CdkTreeModule } from '@angular/cdk/tree';\nimport { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { appChevronDownIcon, appChevronRightIcon, appCircle, PupaIconsModule } from '@bimeister/pupakit.icons';\nimport { PupaButtonsModule, PupaCheckboxModule, PupaSpinnerModule } from '@bimeister/pupakit.kit';\nimport { TreeNodeSkeletonComponent } from './components/tree-node-skeleton/tree-node-skeleton.component';\nimport { TreeNodeComponent } from './components/tree-node/tree-node.component';\nimport { TreeNodeActionsDirective } from './directives/tree-node-actions.directive';\n\n@NgModule({\n declarations: [TreeNodeComponent, TreeNodeActionsDirective, TreeNodeSkeletonComponent],\n imports: [\n CdkTreeModule,\n CommonModule,\n PupaButtonsModule,\n PupaIconsModule.forFeature([appCircle, appChevronDownIcon, appChevronRightIcon]),\n PupaCheckboxModule,\n PupaSpinnerModule,\n ],\n exports: [TreeNodeActionsDirective, TreeNodeComponent, TreeNodeSkeletonComponent],\n})\nexport class PupaTreeLayoutModule {}\n","import { FlatTreeItemInterface } from '../interfaces/flat-tree-item.interface';\n\n/**\n * todo: maybe drop one property – isExpandable or isElement\n * */\nexport class FlatTreeItem<T = any> implements FlatTreeItemInterface<T> {\n constructor(\n public readonly isExpandable: boolean,\n public name: string,\n public level: number,\n public id: string,\n public originalData: T = null,\n public isElement: boolean = false\n ) {}\n}\n","import { DataSource, ListRange } from '@angular/cdk/collections';\nimport { isNil, shareReplayWithRefCount } from '@bimeister/utilities';\nimport { BehaviorSubject, combineLatest, Observable } from 'rxjs';\nimport { filter, map } from 'rxjs/operators';\nimport { FlatTreeItem } from './flat-tree-item.class';\n\nexport class TreeRangedDataSource extends DataSource<FlatTreeItem> {\n private readonly range$: BehaviorSubject<ListRange> = new BehaviorSubject(null);\n\n constructor(private readonly data$: Observable<FlatTreeItem[]>) {\n super();\n }\n\n public setRange(range: ListRange): void {\n this.range$.next(range);\n }\n\n public connect(): Observable<FlatTreeItem[]> {\n return combineLatest([this.data$, this.range$]).pipe(\n filter(([data, range]: [FlatTreeItem[], ListRange]) => Array.isArray(data) && !isNil(range)),\n map(([data, range]: [FlatTreeItem[], ListRange]) => {\n const { start, end }: ListRange = range;\n return data.slice(start, end);\n }),\n shareReplayWithRefCount()\n );\n }\n\n public disconnect(): void {\n return;\n }\n}\n","import { BusEventBase } from '@bimeister/pupakit.common';\nimport { getUuid, Nullable } from '@bimeister/utilities';\nimport { FlatTreeItem } from '../classes/flat-tree-item.class';\nimport { DropEventInterface } from '../interfaces/drop-event.interface';\n\ninterface SetChildrenEventPayload {\n treeItemId: Nullable<string>;\n children: FlatTreeItem[];\n}\n\nexport namespace TreeEvents {\n export class TreeEventBase<T = unknown> extends BusEventBase<T> {\n public readonly scope: Nullable<string> = null;\n public readonly id: string = getUuid();\n public readonly type: Nullable<string> = null;\n\n constructor(public readonly payload: T = null, public readonly fromId: Nullable<string> = null) {\n super();\n }\n }\n\n export class Click extends TreeEventBase<FlatTreeItem> {}\n export class RemoveItem extends TreeEventBase<string> {}\n export class UpdateItem extends TreeEventBase<FlatTreeItem> {}\n export class ScrollById extends TreeEventBase<string> {}\n export class Drop extends TreeEventBase<DropEventInterface<FlatTreeItem>> {}\n export class ScrollByIndex extends TreeEventBase<number> {}\n export class ScrollViewport extends TreeEventBase<number> {}\n export class SetData extends TreeEventBase<FlatTreeItem[]> {}\n export class SetLoading extends TreeEventBase<boolean> {}\n export class SetSelected extends TreeEventBase<string[]> {}\n export class Collapse extends TreeEventBase<string> {}\n export class Expand extends TreeEventBase<string> {}\n export class ExpandWhileDragging extends TreeEventBase<string> {}\n export class RemoveChildren extends TreeEventBase<string> {}\n export class SetExpanded extends TreeEventBase<string[]> {}\n export class SetChildren extends TreeEventBase<SetChildrenEventPayload> {}\n}\n","import { Directive, TemplateRef } from '@angular/core';\nimport { TreeItemTemplateContext } from '../../../declarations/interfaces/tree-item-template-context.interface';\n\n@Directive({\n selector: '[pupaTreeItemTemplate]',\n})\nexport class TreeItemTemplateDirective<T> {\n constructor(public readonly templateRef: TemplateRef<TreeItemTemplateContext<T>>) {}\n}\n","import { ListRange } from '@angular/cdk/collections';\nimport { FlatTreeControl } from '@angular/cdk/tree';\nimport {\n AfterViewInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ContentChild,\n ElementRef,\n HostListener,\n Input,\n OnChanges,\n OnDestroy,\n Renderer2,\n TrackByFunction,\n Type,\n ViewChild,\n} from '@angular/core';\nimport { EventBus } from '@bimeister/event-bus/rxjs';\nimport { filterNotNil, filterTruthy, getClampedValue, isNil, Nullable } from '@bimeister/utilities';\nimport {\n animationFrameScheduler,\n BehaviorSubject,\n combineLatest,\n interval,\n NEVER,\n Observable,\n Subject,\n Subscription,\n timer,\n} from 'rxjs';\nimport { debounce, filter, map, observeOn, switchMap, take, withLatestFrom } from 'rxjs/operators';\nimport { FlatTreeItem } from '../../../../declarations/classes/flat-tree-item.class';\nimport { TreeRangedDataSource } from '../../../../declarations/classes/tree-ranged-data-source.class';\nimport { TreeController } from '../../../../declarations/classes/tree-controller.class';\nimport { TreeEvents } from '../../../../declarations/events/tree.events';\nimport { DropEventInterface } from '../../../../declarations/interfaces/drop-event.interface';\nimport { TreeDataDisplayCollectionRef } from '../../../../declarations/interfaces/tree-data-display-collection-ref.interface';\nimport { TreeItemTemplateDirective } from '../../directives/tree-item-template.directive';\nimport { TreeNodeProperties } from '../../../../declarations/interfaces/tree-node-properties.interface';\nimport { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';\nimport { ComponentChange, ComponentChanges, QueueEvents } from '@bimeister/pupakit.common';\n\ninterface Position {\n top: number;\n left: number;\n}\ntype ScrollDirection = null | 'up' | 'down';\n\nconst EXPAND_WHILE_DRAGGING_DELAY: number = 1000;\n\ninterface DragAndDropMeta {\n dragTreeItem: FlatTreeItem;\n dropTreeItem?: FlatTreeItem;\n dragTreeItemLeft?: number;\n dragTreeItemTop?: number;\n dragTreeItemWidth?: number;\n mouseDownPosition?: Position;\n scrollDirection?: ScrollDirection;\n draggableElementBoundingBox?: DOMRect;\n dragTreeItemIsDisplayed: boolean;\n}\n\n@Component({\n selector: 'pupa-tree-new',\n templateUrl: './tree-new.component.html',\n styleUrls: ['./tree-new.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TreeNewComponent<T> implements AfterViewInit, OnChanges, OnDestroy {\n @Input() public controller: TreeController;\n @ViewChild('skeletonViewPort', { static: true }) public readonly skeletonViewPort: CdkVirtualScrollViewport;\n @ViewChild('viewPort', { static: true }) public readonly viewPort: CdkVirtualScrollViewport;\n @ContentChild(TreeItemTemplateDirective) public readonly treeItemTemplate: TreeItemTemplateDirective<T>;\n\n public dataSource: TreeRangedDataSource;\n public dataDisplayCollection: TreeDataDisplayCollectionRef;\n public data$: Observable<FlatTreeItem[]>;\n public expandedIdsList$: Observable<string[]>;\n public selectedIdsList$: Observable<string[]>;\n public isLoading$: Observable<boolean>;\n public hasDragAndDrop$: Observable<boolean>;\n public trackBy$: Observable<TrackByFunction<FlatTreeItem>>;\n public treeItemSizePx$: Observable<number>;\n private scrollBehavior$: Observable<ScrollBehavior>;\n private eventBus: EventBus;\n\n public readonly treeControl: FlatTreeControl<FlatTreeItem> = new FlatTreeControl<FlatTreeItem>(\n TreeNewComponent.getLevel,\n TreeNewComponent.isExpandable\n );\n public readonly listRange$: BehaviorSubject<ListRange> = new BehaviorSubject(null);\n public readonly dragAndDropMeta$: BehaviorSubject<Nullable<DragAndDropMeta>> = new BehaviorSubject(null);\n private readonly expandWithDelay$: Subject<Nullable<FlatTreeItem>> = new Subject<Nullable<FlatTreeItem>>();\n private readonly subscription: Subscription = new Subscription();\n\n constructor(\n public readonly renderer: Renderer2,\n public readonly host: ElementRef<HTMLElement>,\n private readonly changeDetectorRef: ChangeDetectorRef\n ) {}\n\n public ngAfterViewInit(): void {\n this.subscription.add(this.getSubscriptionToSetLoading());\n this.subscription.add(this.getSubscriptionToScrollToIndex());\n this.subscription.add(this.getSubscriptionToSetExpanded());\n this.subscription.add(this.getSubscriptionToSetSelected());\n this.subscription.add(this.getSubscriptionForScrollWithDrag());\n this.subscription.add(this.getSubscriptionForRangeChanges());\n this.subscription.add(this.getSubscriptionToExpandWhileDragging());\n this.eventBus.dispatch(new QueueEvents.StartQueue());\n }\n\n public ngOnChanges(changes: ComponentChanges<this>): void {\n this.setupController(changes?.controller);\n }\n\n public ngOnDestroy(): void {\n this.subscription.unsubscribe();\n }\n\n public isExpanded(expandedIdsList: string[], treeItem: FlatTreeItem): boolean {\n return !isNil(treeItem) && treeItem.isExpandable && !treeItem.isElement && expandedIdsList.includes(treeItem.id);\n }\n\n public hasChild(_: number, treeItem: FlatTreeItem): boolean {\n return !isNil(treeItem) && treeItem.isExpandable && !treeItem.isElement;\n }\n\n public hasNoChild(_: number, treeItem: FlatTreeItem): boolean {\n return !isNil(treeItem) && !treeItem.isExpandable && !treeItem.isElement;\n }\n\n public isElement(_: number, treeItem: FlatTreeItem): boolean {\n return !isNil(treeItem) && !treeItem.isExpandable && treeItem.isElement;\n }\n\n public idsIncludesNodeId(idsList: string[], treeItem: FlatTreeItem): boolean {\n return idsList.includes(treeItem.id);\n }\n\n public mouseDown(dragTreeItem: FlatTreeItem, event: MouseEvent): void {\n this.hasDragAndDrop$.pipe(take(1), filterTruthy()).subscribe(() => {\n const mouseDownPosition: Position = { left: event?.screenX, top: event?.screenY };\n const target: EventTarget = event.target;\n const draggableElementBoundingBox: DOMRect =\n target instanceof HTMLElement ? target.getBoundingClientRect() : null;\n const dragTreeItemWidth: number = draggableElementBoundingBox?.width;\n this.dragAndDropMeta$.next({\n mouseDownPosition,\n dragTreeItem,\n dropTreeItem: dragTreeItem,\n draggableElementBoundingBox,\n dragTreeItemWidth,\n dragTreeItemIsDisplayed: false,\n });\n });\n }\n\n public mouseEnter(treeItem: FlatTreeItem): void {\n this.dragAndDropMeta$.pipe(take(1), filterNotNil()).subscribe((dragAndDropMeta: DragAndDropMeta) => {\n this.dragAndDropMeta$.next({ ...dragAndDropMeta, dropTreeItem: treeItem });\n this.expandWithDelay$.next(treeItem);\n });\n }\n\n public mouseLeave(): void {\n this.dragAndDropMeta$.pipe(take(1), filterNotNil()).subscribe((dragAndDropMeta: DragAndDropMeta) => {\n this.dragAndDropMeta$.next({ ...dragAndDropMeta, dropTreeItem: null });\n });\n }\n\n @HostListener('window:mousemove', ['$event'])\n public mouseMove(event: MouseEvent): void {\n this.dragAndDropMeta$.pipe(take(1), filterNotNil()).subscribe((dragAndDropMeta: DragAndDropMeta) => {\n this.processDragMoving(dragAndDropMeta, event.screenX, event.screenY);\n });\n }\n\n @HostListener('window:mouseup')\n public mouseUp(): void {\n this.dragAndDropMeta$.pipe(take(1), filterNotNil()).subscribe((dragAndDropMeta: DragAndDropMeta) => {\n if (!dragAndDropMeta.dragTreeItemIsDisplayed) {\n return;\n }\n const dropEventData: DropEventInterface<FlatTreeItem> = {\n draggedElement: dragAndDropMeta.dragTreeItem,\n droppedElement: dragAndDropMeta.dropTreeItem,\n };\n this.eventBus.dispatch(new TreeEvents.Drop(dropEventData));\n });\n this.expandWithDelay$.next(null);\n this.dragAndDropMeta$.next(null);\n }\n\n public click(treeItem: FlatTreeItem): void {\n this.eventBus.dispatch(new TreeEvents.Click(treeItem));\n }\n\n public toggleExpansion(expandedIdsList: string[], treeItem: FlatTreeItem): void {\n this.isExpanded(expandedIdsList, treeItem) ? this.collapseClick(treeItem) : this.expandClick(treeItem);\n }\n\n public getTreeNodeProperties(node: FlatTreeItem): Observable<TreeNodeProperties> {\n return combineLatest([this.expandedIdsList$, this.selectedIdsList$]).pipe(\n map(\n ([expandedIdsList, selectedIdsList]: [string[], string[]]): TreeNodeProperties => ({\n level: node.level,\n isDirectory: !node.isElement,\n hasChildren: node.isExpandable,\n isLoading: false,\n isExpanded: this.isExpanded(expandedIdsList, node),\n isSelected: this.idsIncludesNodeId(selectedIdsList, node),\n isHighlighted: this.idsIncludesNodeId(selectedIdsList, node),\n isDisabled: false,\n expand: () => {\n this.toggleExpansion(expandedIdsList, node);\n },\n })\n )\n );\n }\n\n private setupController(change: ComponentChange<this, TreeController>): void {\n const value: Nullable<TreeController> = change?.currentValue;\n if (isNil(value)) {\n return;\n }\n this.controller = value;\n\n this.dataDisplayCollection = this.controller.getDataDisplayCollectionRef();\n this.eventBus = this.controller.eventBus;\n this.scrollBehavior$ = this.dataDisplayCollection.scrollBehavior$;\n this.trackBy$ = this.dataDisplayCollection.trackBy$;\n this.treeItemSizePx$ = this.dataDisplayCollection.treeItemSizePx$;\n this.data$ = this.dataDisplayCollection.data$;\n this.isLoading$ = this.dataDisplayCollection.isLoading$;\n this.selectedIdsList$ = this.dataDisplayCollection.selectedIdsList$;\n this.expandedIdsList$ = this.dataDisplayCollection.expandedIdsList$;\n this.hasDragAndDrop$ = this.dataDisplayCollection.hasDragAndDrop$;\n\n this.dataSource = new TreeRangedDataSource(this.data$);\n }\n\n private expandClick(treeItem: FlatTreeItem): void {\n this.controller.expand(treeItem.id);\n }\n\n private collapseClick(treeItem: FlatTreeItem): void {\n this.eventBus.dispatch(new TreeEvents.Collapse(treeItem.id));\n }\n\n private getSubscriptionToExpandWhileDragging(): Subscription {\n return this.expandWithDelay$\n .pipe(\n debounce((node: FlatTreeItem) => (isNil(node) ? NEVER : timer(EXPAND_WHILE_DRAGGING_DELAY))),\n filter((treeItem: FlatTreeItem) => treeItem.isExpandable && !treeItem.isElement)\n )\n .subscribe((treeItem: FlatTreeItem) => {\n this.eventBus.dispatch(new TreeEvents.ExpandWhileDragging(treeItem.id));\n });\n }\n\n private getSubscriptionToSetLoading(): Subscription {\n return this.getEvents(TreeEvents.SetLoading).subscribe((event: TreeEvents.SetLoading) => {\n this.dataDisplayCollection.setIsLoading(event.payload);\n this.changeDetectorRef.detectChanges();\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n private getSubscriptionToSetExpanded(): Subscription {\n return this.getEvents(TreeEvents.SetExpanded).subscribe((event: TreeEvents.SetExpanded) => {\n this.setExpandedIdsList(event.payload);\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n private setExpandedIdsList(list: string[]): void {\n this.dataDisplayCollection.setExpandedIdsList(list);\n this.data$\n .pipe(\n take(1),\n map((data: FlatTreeItem[]) => data.filter((treeItem: FlatTreeItem) => list.includes(treeItem.id)))\n )\n .subscribe((data: FlatTreeItem[]) => {\n data.forEach((treeItem: FlatTreeItem) => this.treeControl.expand(treeItem));\n });\n this.changeDetectorRef.markForCheck();\n }\n\n private getSubscriptionToSetSelected(): Subscription {\n return this.getEvents(TreeEvents.SetSelected).subscribe((event: TreeEvents.SetSelected) => {\n this.setSelectedIdsList(event.payload);\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n private setSelectedIdsList(list: string[]): void {\n this.dataDisplayCollection.setSelectedIdsList(list);\n this.changeDetectorRef.detectChanges();\n }\n\n private getSubscriptionToScrollToIndex(): Subscription {\n return this.getEvents(TreeEvents.ScrollByIndex).subscribe((event: TreeEvents.ScrollByIndex) => {\n this.scrollToIndex(event.payload);\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n private scrollToIndex(index: number): void {\n this.scrollBehavior$.pipe(take(1)).subscribe((scrollBehavior: ScrollBehavior) => {\n this.viewPort.scrollToIndex(index, scrollBehavior);\n this.skeletonViewPort.scrollToIndex(index, scrollBehavior);\n });\n }\n\n private getSubscriptionForScrollWithDrag(): Subscription {\n return interval(0)\n .pipe(\n observeOn(animationFrameScheduler),\n switchMap(() => this.dragAndDropMeta$.pipe(take(1))),\n filterNotNil(),\n map((dragAndDropMeta: DragAndDropMeta) => dragAndDropMeta.scrollDirection),\n filterNotNil(),\n withLatestFrom(this.scrollBehavior$)\n )\n .subscribe(([scrollDirection, scrollBehavior]: [ScrollDirection, ScrollBehavior]) => {\n const scrollingSpeed: number = 5;\n const offsetDelta: number = scrollDirection === 'up' ? -scrollingSpeed : scrollingSpeed;\n const currentOffset: number = this.viewPort.measureScrollOffset();\n this.viewPort.scrollToOffset(currentOffset + offsetDelta, scrollBehavior);\n });\n }\n\n private getSubscriptionForRangeChanges(): Subscription {\n return this.viewPort.renderedRangeStream.subscribe((range: ListRange) => {\n this.dataSource.setRange(range);\n });\n }\n\n private getEvents<E extends TreeEvents.TreeEventBase>(eventType: Type<E>): Observable<E> {\n return this.eventBus\n .listen()\n .pipe(filter((event: TreeEvents.TreeEventBase): event is E => event instanceof eventType));\n }\n\n private processDragMoving(dragAndDropMeta: DragAndDropMeta, x: number, y: number): void {\n const draggableElementBoundingBox: DOMRect = dragAndDropMeta.draggableElementBoundingBox;\n const mouseDownPosition: Position = dragAndDropMeta.mouseDownPosition;\n const draggableElementPositionShift: Position = {\n left: mouseDownPosition?.left - draggableElementBoundingBox?.left,\n top: mouseDownPosition?.top - draggableElementBoundingBox?.top,\n };\n const bottomBorderPositionY: number = this.host.nativeElement.clientHeight - draggableElementBoundingBox?.height;\n const newMeta: DragAndDropMeta = {\n ...dragAndDropMeta,\n dragTreeItemLeft: x - draggableElementBoundingBox?.left - draggableElementPositionShift.left,\n dragTreeItemTop: getClampedValue(y - draggableElementPositionShift.top, 0, bottomBorderPositionY),\n };\n const isTopBorderReached: boolean = newMeta.dragTreeItemTop <= draggableElementBoundingBox?.height;\n const isBottomBorderReached: boolean = newMeta.dragTreeItemTop >= bottomBorderPositionY;\n newMeta.scrollDirection = isTopBorderReached ? 'up' : isBottomBorderReached ? 'down' : null;\n newMeta.dragTreeItemIsDisplayed = true;\n\n this.dragAndDropMeta$.next(newMeta);\n }\n\n private static getLevel(treeItem: FlatTreeItem): number {\n return treeItem.level;\n }\n\n private static isExpandable(treeItem: FlatTreeItem): boolean {\n return treeItem.isExpandable;\n }\n}\n","<section class=\"tree\">\n <div class=\"tree__loader\" *ngIf=\"isLoading$ | async\">\n <pupa-spinner size=\"40px\"></pupa-spinner>\n </div>\n\n <pupa-scrollable [invisibleScrollbars]=\"['vertical', 'horizontal']\" class=\"skeleton-scrollable\">\n <cdk-virtual-scroll-viewport\n pupaScrollableContent\n #skeletonViewPort\n [itemSize]=\"treeItemSizePx$ | async\"\n class=\"viewport viewport_skeleton\"\n >\n <pupa-skeleton>\n <pupa-skeleton-group *cdkVirtualFor=\"let item of data$ | async\">\n <pupa-tree-node-skeleton [level]=\"item.level\"></pupa-tree-node-skeleton>\n </pupa-skeleton-group>\n </pupa-skeleton>\n </cdk-virtual-scroll-viewport>\n </pupa-scrollable>\n\n <pupa-scrollable>\n <cdk-virtual-scroll-viewport pupaScrollableContent #viewPort [itemSize]=\"treeItemSizePx$ | async\" class=\"viewport\">\n <ng-container *cdkVirtualFor=\"let treeItem of data$ | async\"> </ng-container>\n <cdk-tree\n [dataSource]=\"dataSource\"\n [treeControl]=\"treeControl\"\n [trackBy]=\"trackBy$ | async\"\n (mouseleave)=\"mouseLeave()\"\n class=\"scrollable-box__container\"\n >\n <cdk-tree-node\n *cdkTreeNodeDef=\"let node; when: isElement\"\n (mousedown)=\"mouseDown(node, $event)\"\n (mouseenter)=\"mouseEnter(node)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"treeItemTemplate?.templateRef || defaultTemplate\"\n [ngTemplateOutletContext]=\"{\n $implicit: node\n }\"\n ></ng-container>\n </cdk-tree-node>\n\n <cdk-tree-node\n *cdkTreeNodeDef=\"let node; when: hasNoChild\"\n (mousedown)=\"mouseDown(node, $event)\"\n (mouseenter)=\"mouseEnter(node)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"treeItemTemplate?.templateRef || defaultTemplate\"\n [ngTemplateOutletContext]=\"{\n $implicit: node\n }\"\n ></ng-container>\n </cdk-tree-node>\n\n <cdk-tree-node\n *cdkTreeNodeDef=\"let node; when: hasChild\"\n (mousedown)=\"mouseDown(node, $event)\"\n (mouseenter)=\"mouseEnter(node)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"treeItemTemplate?.templateRef || defaultTemplate\"\n [ngTemplateOutletContext]=\"{\n $implicit: node\n }\"\n ></ng-container>\n </cdk-tree-node>\n </cdk-tree>\n </cdk-virtual-scroll-viewport>\n </pupa-scrollable>\n\n <ng-container *ngIf=\"dragAndDropMeta$ | async as dragAndDropMeta\">\n <ng-container\n [ngTemplateOutlet]=\"dragTreeItemTemplate\"\n [ngTemplateOutletContext]=\"{\n treeItem: dragAndDropMeta.dragTreeItem,\n top: dragAndDropMeta.dragTreeItemTop,\n left: dragAndDropMeta.dragTreeItemLeft,\n width: dragAndDropMeta.dragTreeItemWidth,\n isVisible: dragAndDropMeta.dragTreeItemIsDisplayed\n }\"\n ></ng-container>\n </ng-container>\n</section>\n\n<ng-template\n let-left=\"left\"\n let-width=\"width\"\n let-top=\"top\"\n let-treeItem=\"treeItem\"\n let-isVisible=\"isVisible\"\n #dragTreeItemTemplate\n>\n <div *ngIf=\"isVisible\" class=\"draggable\" [style.left.px]=\"left\" [style.top.px]=\"top\" [style.width.px]=\"width\">\n <ng-container\n [ngTemplateOutlet]=\"treeItemTemplate?.templateRef || defaultTemplate\"\n [ngTemplateOutletContext]=\"{\n $implicit: treeItem\n }\"\n ></ng-container>\n </div>\n</ng-template>\n\n<ng-template #defaultTemplate let-node>\n <pupa-tree-node (click)=\"click(node)\" [treeNodeProperties]=\"getTreeNodeProperties(node) | async\">{{\n node?.name\n }}</pupa-tree-node>\n</ng-template>\n\n<ng-content></ng-content>\n","import { ScrollingModule } from '@angular/cdk/scrolling';\nimport { CdkTreeModule } from '@angular/cdk/tree';\nimport { NgModule } from '@angular/core';\n\nimport { CommonModule } from '@angular/common';\nimport { iosArrowDownIcon, iosArrowForwardIcon, PupaIconsModule } from '@bimeister/pupakit.icons';\nimport { PupaScrollableModule, PupaSkeletonModule, PupaSpinnerModule } from '@bimeister/pupakit.kit';\nimport { PupaTreeLayoutModule } from '../tree-layout/tree-layout.module';\nimport { TreeNewComponent } from './components/tree-new/tree-new.component';\nimport { TreeItemTemplateDirective } from './directives/tree-item-template.directive';\n\n@NgModule({\n declarations: [TreeNewComponent, TreeItemTemplateDirective],\n imports: [\n CommonModule,\n PupaSkeletonModule,\n PupaSpinnerModule,\n ScrollingModule,\n CdkTreeModule,\n PupaIconsModule.forFeature([iosArrowDownIcon, iosArrowForwardIcon]),\n PupaTreeLayoutModule,\n PupaScrollableModule,\n ],\n providers: [],\n exports: [TreeNewComponent, TreeItemTemplateDirective],\n})\nexport class PupaTreeNewModule {}\n","import { NgModule, Type } from '@angular/core';\nimport { PupaTreeLayoutModule } from './components/tree-layout/tree-layout.module';\nimport { PupaTreeNewModule } from './components/tree-new/tree-new.module';\n\nconst MODULES: Type<unknown>[] = [PupaTreeLayoutModule, PupaTreeNewModule];\n\n@NgModule({\n declarations: [],\n imports: MODULES,\n exports: MODULES,\n})\nexport class PupaTreeModule {}\n","export const DEFAULT_TREE_ITEM_SIZE_PX: number = 32;\n","import { TrackByFunction } from '@angular/core';\nimport { isNil } from '@bimeister/utilities';\nimport { DEFAULT_TREE_ITEM_SIZE_PX } from '../constants/default-tree-item-size-px.const';\nimport { BehaviorSubject, Observable, Subject } from 'rxjs';\nimport { take } from 'rxjs/operators';\nimport { TreeDataDisplayCollectionRef } from '../interfaces/tree-data-display-collection-ref.interface';\nimport { FlatTreeItem } from './flat-tree-item.class';\n\nexport class TreeDataDisplayCollection implements TreeDataDisplayCollectionRef {\n public readonly data$: BehaviorSubject<FlatTreeItem[]> = new BehaviorSubject<FlatTreeItem[]>([]);\n public readonly selectedIdsList$: BehaviorSubject<string[]> = new BehaviorSubject([]);\n\n public readonly trackBy$: Subject<TrackByFunction<FlatTreeItem>> = new BehaviorSubject<TrackByFunction<FlatTreeItem>>(\n TreeDataDisplayCollection.trackBy\n );\n public readonly scrollBehavior$: BehaviorSubject<ScrollBehavior> = new BehaviorSubject('smooth');\n public readonly expandedIdsList$: BehaviorSubject<string[]> = new BehaviorSubject([]);\n public readonly hasDragAndDrop$: BehaviorSubject<boolean> = new BehaviorSubject(false);\n public readonly treeItemSizePx$: BehaviorSubject<number> = new BehaviorSubject(DEFAULT_TREE_ITEM_SIZE_PX);\n public readonly isLoading$: BehaviorSubject<boolean> = new BehaviorSubject(false);\n\n public setExpandedIdsList(value: string[]): void {\n this.expandedIdsList$.next(value);\n }\n\n public setIsLoading(value: boolean): void {\n this.isLoading$.next(value);\n }\n\n public setData(data: FlatTreeItem[]): Observable<FlatTreeItem[]> {\n this.data$.next(data);\n return this.data$.pipe(take(1));\n }\n\n public setSelectedIdsList(value: string[]): void {\n this.selectedIdsList$.next(value);\n }\n\n public static readonly trackBy: TrackByFunction<any> = <T>(index: number, dataItem: T): string => {\n if (isNil(dataItem)) {\n return `${index}__null`;\n }\n return `${index}__${JSON.stringify(dataItem)}`;\n };\n}\n","import { Type } from '@angular/core';\nimport { EventBus } from '@bimeister/event-bus/rxjs';\nimport { QueueEvents } from '@bimeister/pupakit.common';\nimport { isNil } from '@bimeister/utilities';\nimport { Observable, Subscription } from 'rxjs';\nimport { filter, mapTo, switchMap, withLatestFrom } from 'rxjs/operators';\nimport { TreeEvents } from '../events/tree.events';\nimport { FlatTreeItem } from './flat-tree-item.class';\nimport { TreeDataDisplayCollection } from './tree-data-display-collection.class';\n\nexport class DefaultTreeEventHandler {\n protected subscription: Subscription = new Subscription();\n\n constructor(\n protected readonly eventBus: EventBus,\n protected readonly dataDisplayCollection: TreeDataDisplayCollection\n ) {\n this.reconnect();\n }\n\n protected subscribeToEvents(): void {\n this.subscription.add(this.getSubscriptionToSetData());\n this.subscription.add(this.getSubscriptionForUpdateItem());\n this.subscription.add(this.getSubscriptionForRemoveItem());\n this.subscription.add(this.getSubscriptionForScrollTo());\n this.subscription.add(this.getSubscriptionForRemoveChildren());\n this.subscription.add(this.getSubscriptionForSetChildren());\n }\n\n protected getSubscriptionToSetData(): Subscription {\n return this.getEvents(TreeEvents.SetData)\n .pipe(\n switchMap((event: TreeEvents.SetData) => this.dataDisplayCollection.setData(event.payload).pipe(mapTo(event)))\n )\n .subscribe((event: TreeEvents.SetData) => this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id)));\n }\n\n protected getSubscriptionForScrollTo(): Subscription {\n return this.getEvents(TreeEvents.ScrollById)\n .pipe(withLatestFrom(this.dataDisplayCollection.data$))\n .subscribe(([event, data]: [TreeEvents.ScrollById, FlatTreeItem[]]) => {\n const treeItem: FlatTreeItem = DefaultTreeEventHandler.getTreeItem(event.payload, data);\n if (isNil(treeItem)) {\n return this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n }\n const index: number = data.indexOf(treeItem);\n this.eventBus.dispatch(new TreeEvents.ScrollByIndex(index));\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n protected getSubscriptionForRemoveItem(): Subscription {\n return this.getEvents(TreeEvents.RemoveItem)\n .pipe(withLatestFrom(this.dataDisplayCollection.data$, this.dataDisplayCollection.expandedIdsList$))\n .subscribe(([event, data, expandedIdsList]: [TreeEvents.RemoveItem, FlatTreeItem[], string[]]) => {\n this.removeItemWithChildren(event.payload, data, expandedIdsList);\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n protected getSubscriptionForUpdateItem(): Subscription {\n return this.getEvents(TreeEvents.UpdateItem)\n .pipe(withLatestFrom(this.dataDisplayCollection.data$))\n .subscribe(([event, data]: [TreeEvents.UpdateItem, FlatTreeItem[]]) => {\n this.updateItem(event.payload, data);\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n protected updateItem(updatedItem: FlatTreeItem, data: FlatTreeItem[]): void {\n const treeItemExists: boolean = DefaultTreeEventHandler.treeItemExists(updatedItem.id, data);\n if (!treeItemExists) {\n return;\n }\n const updatedData: FlatTreeItem[] = data.map((treeItem: FlatTreeItem) => {\n if (treeItem.id === updatedItem.id) {\n return new FlatTreeItem(\n treeItem.isExpandable,\n updatedItem.name,\n treeItem.level,\n treeItem.id,\n updatedItem.originalData,\n treeItem.isElement\n );\n }\n return treeItem;\n });\n this.eventBus.dispatch(new TreeEvents.SetData(updatedData));\n }\n\n protected removeItem(removeItemId: string, data: FlatTreeItem[]): void {\n const treeItemExists: boolean = DefaultTreeEventHandler.treeItemExists(removeItemId, data);\n if (!treeItemExists) {\n return;\n }\n const updatedData: FlatTreeItem[] = data.filter((treeItem: FlatTreeItem) => treeItem.id !== removeItemId);\n this.eventBus.dispatch(new TreeEvents.SetData(updatedData));\n }\n\n public reconnect(): void {\n this.disconnect();\n this.subscribeToEvents();\n }\n\n public disconnect(): void {\n this.subscription.unsubscribe();\n this.subscription = new Subscription();\n }\n\n public getEvents<E extends TreeEvents.TreeEventBase>(eventType: Type<E>): Observable<E> {\n return this.eventBus\n .listen()\n .pipe(filter((event: TreeEvents.TreeEventBase): event is E => event instanceof eventType));\n }\n\n private getSubscriptionForSetChildren(): Subscription {\n return this.getEvents(TreeEvents.SetChildren)\n .pipe(withLatestFrom(this.dataDisplayCollection.data$, this.dataDisplayCollection.expandedIdsList$))\n .subscribe(([event, data, expandedIdsList]: [TreeEvents.SetChildren, FlatTreeItem[], string[]]) => {\n this.setChildren(event.payload.treeItemId, event.payload.children, data, expandedIdsList);\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n private getSubscriptionForRemoveChildren(): Subscription {\n return this.getEvents(TreeEvents.RemoveChildren)\n .pipe(withLatestFrom(this.dataDisplayCollection.data$, this.dataDisplayCollection.expandedIdsList$))\n .subscribe(([event, data, expandedIdsList]: [TreeEvents.RemoveChildren, FlatTreeItem[], string[]]) => {\n this.removeChildren(event.payload, data, expandedIdsList);\n this.eventBus.dispatch(new QueueEvents.RemoveFromQueue(event.id));\n });\n }\n\n private setChildren(\n parentId: string,\n children: FlatTreeItem[],\n data: FlatTreeItem[],\n expandedIdsList: string[]\n ): void {\n if (isNil(parentId)) {\n const newData: FlatTreeItem[] = children.map((treeItem: FlatTreeItem) => ({ ...treeItem, level: 0 }));\n this.eventBus.dispatch(new TreeEvents.SetData(newData));\n this.eventBus.dispatch(new TreeEvents.SetExpanded([]));\n return;\n }\n const parentIsExpanded: boolean = expandedIdsList.includes(parentId);\n if (parentIsExpanded) {\n return;\n }\n const parent: FlatTreeItem = DefaultTreeEventHandler.getTreeItem(parentId, data);\n const isExpanded: boolean = expandedIdsList.includes(parentId);\n if (isNil(parent) || isExpanded) {\n return;\n }\n const childrenIdsSet: Set<string> = new Set();\n const childrenWithLevel: FlatTreeItem[] = children.map((childItem: FlatTreeItem) => {\n childrenIdsSet.add(childItem.id);\n return {\n ...childItem,\n level: Number(parent.level) + 1,\n };\n });\n const parentIndex: number = data.indexOf(parent);\n const dataWithChildren: FlatTreeItem[] = [\n ...data.slice(0, parentIndex),\n parent,\n ...childrenWithLevel,\n ...data.slice(parentIndex + 1),\n ];\n const noExpandedChildrenList: string[] = expandedIdsList.filter(\n (expandedTreeItemId: string) => !childrenIdsSet.has(expandedTreeItemId)\n );\n const newExpandedList: string[] = [...noExpandedChildrenList, parent.id];\n this.eventBus.dispatch(new TreeEvents.SetData(dataWithChildren));\n this.eventBus.dispatch(new TreeEvents.SetExpanded(newExpandedList));\n }\n\n private removeItemWithChildren(removeItemId: string, data: FlatTreeItem[], expanded: string[]): void {\n const treeItemExists: boolean = DefaultTreeEventHandler.treeItemExists(removeItemId, data);\n if (!treeItemExists) {\n this.eventBus.dispatch(new TreeEvents.SetData(data));\n this.eventBus.dispatch(new TreeEvents.SetExpanded(expanded));\n return;\n }\n const [dataWithoutChildren, expandedWithoutChildren]: [FlatTreeItem[], string[]] = this.getRemovedChildren(\n removeItemId,\n data,\n expanded\n );\n const dataWithoutRemovedItem: FlatTreeItem[] = dataWithoutChildren.filter(\n (treeItem: FlatTreeItem) => treeItem.id !== removeItemId\n );\n this.eventBus.dispatch(new TreeEvents.SetData(dataWithoutRemovedItem));\n this.eventBus.dispatch(new TreeEvents.SetExpanded(expandedWithoutChildren));\n }\n\n private removeChildren(parentId: string, data: FlatTreeItem[], expanded: string[]): void {\n const [dataWithoutChildren, expandedWithoutChildren]: [FlatTreeItem[], string[]] = this.getRemovedChildren(\n parentId,\n data,\n expanded\n );\n this.eventBus.dispatch(new TreeEvents.SetData(dataWithoutChildren));\n this.eventBus.dispatch(new TreeEvents.SetExpanded(expandedWithoutChildren));\n }\n\n private getRemovedChildren(parentId: string, data: FlatTreeItem[], expanded: string[]): [FlatTreeItem[], string[]] {\n const parent: FlatTreeItem = DefaultTreeEventHandler.getTreeItem(parentId, data);\n if (isNil(parent)) {\n return [data, expanded];\n }\n const parentIndex: number = data.indexOf(parent);\n const dataBeforeParent: FlatTreeItem[] = parentIndex === 0 ? [] : data.slice(0, parentIndex);\n const dataAfterParent: FlatTreeItem[] = data.slice(parentIndex + 1);\n const nextNonChildIndex: number = dataAfterParent.findIndex(\n (dataItem: FlatTreeItem) => dataItem.level <= parent.level\n );\n const isLastParent: boolean = nextNonChildIndex === -1;\n const children: FlatTreeItem[] = dataAfterParent.slice(0, nextNonChildIndex);\n const childrenIdsList: string[] = children.map((child: FlatTreeItem) => child.id);\n const expandedWithoutChildren: string[] = expanded.filter(\n (expandedItemId: string) => !childrenIdsList.includes(expandedItemId) && expandedItemId !== parentId\n );\n const dataAfterLastChild: FlatTreeItem[] = isLastParent ? [] : dataAfterParent.slice(nextNonChildIndex);\n const dataWithoutChildren: FlatTreeItem[] = [...dataBeforeParent, parent, ...dataAfterLastChild];\n return [dataWithoutChildren, expandedWithoutChildren];\n }\n\n protected static treeItemExists(treeItemId: string, data: FlatTreeItem[]): boolean {\n const treeItem: FlatTreeItem = DefaultTreeEventHandler.getTreeItem(treeItemId, data);\n return !isNil(treeItem);\n }\n\n protected static getTreeItem(treeItemId: string, data: FlatTreeItem[]): FlatTreeItem {\n return data.find((treeItem: FlatTreeItem) => treeItem.id === treeItemId);\n }\n}\n","import { TrackByFunction, Type } from '@angular/core';\nimport { EventBus } from '@bimeister/event-bus/rxjs';\nimport { Nullable } from '@bimeister/utilities';\nimport { Observable } from 'rxjs';\nimport { TreeEvents } from '../events/tree.events';\nimport { TreeControllerOptions } from '../interfaces/tree-controller-options.interface';\nimport { TreeDataDisplayCollectionRef } from '../interfaces/tree-data-display-collection-ref.interface';\nimport { DefaultTreeEventHandler } from './default-tree-event-handler.class';\nimport { FlatTreeItem } from './flat-tree-item.class';\nimport { TreeDataDisplayCollection } from './tree-data-display-collection.class';\nimport { DEFAULT_TREE_ITEM_SIZE_PX } from '../constants/default-tree-item-size-px.const';\nimport { EventsQueue, QueueEvents } from '@bimeister/pupakit.common';\n\nexport class TreeController {\n public readonly eventBus: EventBus = new EventBus();\n\n protected readonly queue: EventsQueue = new EventsQueue(this.eventBus);\n protected readonly dataDisplayCollection: TreeDataDisplayCollection = new TreeDataDisplayCollection();\n protected readonly handler: DefaultTreeEventHandler = new DefaultTreeEventHandler(\n this.eventBus,\n this.dataDisplayCollection\n );\n\n constructor(protected readonly options?: Nullable<TreeControllerOptions>) {\n this.setScrollBehavior(options?.scrollBehavior);\n this.setTrackBy(options?.trackBy);\n this.setHasDragAndDrop(options?.hasDragAndDrop);\n this.setTreeItemSizePx(options?.treeItemSizePx);\n }\n\n protected dispatchInQueue(event: TreeEvents.TreeEventBase): void {\n const queueEvent: QueueEvents.AddToQueue = new QueueEvents.AddToQueue(event);\n this.eventBus.dispatch(queueEvent);\n }\n\n public getOptions(): Nullable<TreeControllerOptions> {\n return this.options;\n }\n\n public setData(data: FlatTreeItem[]): void {\n this.dispatchInQueue(new TreeEvents.SetData(data));\n }\n\n public setSelected(...selectedIds: string[]): void {\n this.dispatchInQueue(new TreeEvents.SetSelected(selectedIds));\n }\n\n public setLoading(isLoading: boolean): void {\n this.eventBus.dispatch(new TreeEvents.SetLoading(isLoading));\n }\n\n public setHasDragAndDrop(hasDragAndDrop: boolean = false): void {\n this.dataDisplayCollection.hasDragAndDrop$.next(hasDragAndDrop);\n }\n\n public getEvents<E extends TreeEvents.TreeEventBase>(eventType: Type<E>): Observable<E> {\n return this.queue.getEvents(eventType);\n }\n\n public getDataDisplayCollectionRef(): TreeDataDisplayCollectionRef {\n return this.dataDisplayCollection;\n }\n\n public expand(treeItemId: string): void {\n this.eventBus.dispatch(new TreeEvents.Expand(treeItemId));\n }\n\n public setChildren(treeItemId: string, children: FlatTreeItem[]): void {\n this.dispatchInQueue(new TreeEvents.SetChildren({ treeItemId, children }));\n }\n\n public removeChildren(treeItemId: string): void {\n this.dispatchInQueue(new TreeEvents.RemoveChildren(treeItemId));\n }\n\n public removeTreeItem(treeItemId: string): void {\n this.dispatchInQueue(new TreeEvents.RemoveItem(treeItemId));\n }\n\n public setTreeItem(treeItem: FlatTreeItem): void {\n this.dispatchInQueue(new TreeEvents.UpdateItem(treeItem));\n }\n\n public scrollTo(treeItemId: string): void {\n this.dispatchInQueue(new TreeEvents.ScrollById(treeItemId));\n }\n\n private setScrollBehavior(scrollBehavior: ScrollBehavior = 'smooth'): void {\n this.dataDisplayCollection.scrollBehavior$.next(scrollBehavior);\n }\n\n private setTrackBy(trackBy: TrackByFunction<FlatTreeItem> = TreeDataDisplayCollection.trackBy): void {\n this.dataDisplayCollection.trackBy$.next(trackBy);\n }\n\n private setTreeItemSizePx(treeItemSizePx: number = DEFAULT_TREE_ITEM_SIZE_PX): void {\n this.dataDisplayCollection.treeItemSizePx$.next(treeItemSizePx);\n }\n}\n","import { ElementRef, Injectable, Renderer2 } from '@angular/core';\nimport { Uuid } from '@bimeister/pupakit.common';\nimport { getClampedValue, isNil, Nullable } from '@bimeister/utilities';\nimport { BehaviorSubject, Observable, Subject, timer } from 'rxjs';\nimport { take, withLatestFrom } from 'rxjs/operators';\nimport { DropEventInterface } from '../interfaces/drop-event.interface';\nimport { FlatTreeItem } from './flat-tree-item.class';\n\ninterface Position {\n top: number;\n left: number;\n}\n\n/**\n * @deprecated\n */\n@Injectable()\nexport class TreeDragAndDropControl {\n public readonly draggingHasStarted$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);\n public readonly droppedSubject$: Subject<DropEventInterface<FlatTreeItem>> = new Subject();\n public readonly draggableNode$: BehaviorSubject<Nullable<FlatTreeItem>> = new BehaviorSubject<Nullable<FlatTreeItem>>(\n null\n );\n public readonly scrollDirection$: BehaviorSubject<Nullable<'up' | 'down'>> = new BehaviorSubject<\n Nullable<'up' | 'down'>\n >(null);\n public readonly expandNodeWithDelay$: Subject<FlatTreeItem | null> = new Subject<FlatTreeItem | null>();\n\n private readonly hasDragAndDrop$: BehaviorSubject<boolean> = new BehaviorSubject(false);\n /** @deprecated mutable object */\n private dropNode: FlatTreeItem | null;\n /** @deprecated mutable object */\n private mouseDownPosition: Position | null = null;\n /** @deprecated mutable object */\n private draggableElementBoundingBox: DOMRect | null = null;\n private draggableExpandableDescendants: Uuid[] = [];\n private renderer: Renderer2;\n private host: ElementRef<HTMLElement>;\n\n public setHostAndRenderer(host: ElementRef<HTMLElement>, renderer: Renderer2): void {\n this.host = host;\n this