UNPKG

@ephraim-haber/angular-tree-component

Version:

A simple yet powerful tree component for Angular 14+

1 lines 153 kB
{"version":3,"file":"ephraim-haber-angular-tree-component.mjs","sources":["../../../projects/angular-tree-component/src/lib/mobx-angular/tree-mobx-autorun.directive.ts","../../../projects/angular-tree-component/src/lib/constants/keys.ts","../../../projects/angular-tree-component/src/lib/models/tree-options.model.ts","../../../projects/angular-tree-component/src/lib/constants/events.ts","../../../projects/angular-tree-component/src/lib/models/tree-node.model.ts","../../../projects/angular-tree-component/src/lib/models/tree.model.ts","../../../projects/angular-tree-component/src/lib/models/tree-dragged-element.model.ts","../../../projects/angular-tree-component/src/lib/models/tree-virtual-scroll.model.ts","../../../projects/angular-tree-component/src/lib/components/loading.component.ts","../../../projects/angular-tree-component/src/lib/directives/tree-drop.directive.ts","../../../projects/angular-tree-component/src/lib/components/tree-node-drop-slot.component.ts","../../../projects/angular-tree-component/src/lib/mobx-angular/mobx-proxy.ts","../../../projects/angular-tree-component/src/lib/directives/tree-animate-open.directive.ts","../../../projects/angular-tree-component/src/lib/components/tree-node-content.component.ts","../../../projects/angular-tree-component/src/lib/directives/tree-drag.directive.ts","../../../projects/angular-tree-component/src/lib/components/tree-node-expander.component.ts","../../../projects/angular-tree-component/src/lib/components/tree-node-checkbox.component.ts","../../../projects/angular-tree-component/src/lib/components/tree-node-wrapper.component.ts","../../../projects/angular-tree-component/src/lib/components/tree-node-collection.component.ts","../../../projects/angular-tree-component/src/lib/components/tree-viewport.component.ts","../../../projects/angular-tree-component/src/lib/components/tree.component.ts","../../../projects/angular-tree-component/src/lib/angular-tree-component.module.ts","../../../projects/angular-tree-component/src/public-api.ts","../../../projects/angular-tree-component/src/ephraim-haber-angular-tree-component.ts"],"sourcesContent":["import { Directive, ViewContainerRef, TemplateRef, OnInit, OnDestroy, Input, EmbeddedViewRef } from '@angular/core';\nimport { autorun } from 'mobx';\n\n@Directive({\n selector: '[treeMobxAutorun]',\n standalone: false,\n})\nexport class TreeMobxAutorunDirective implements OnInit, OnDestroy {\n protected templateBindings = {};\n protected dispose: any;\n protected view: EmbeddedViewRef<any>;\n @Input() treeMobxAutorun;\n\n constructor(\n protected templateRef: TemplateRef<any>,\n protected viewContainer: ViewContainerRef,\n ) {}\n\n ngOnInit() {\n this.view = this.viewContainer.createEmbeddedView(this.templateRef);\n\n if (this.dispose) {\n this.dispose();\n }\n\n if (this.shouldDetach()) {\n this.view.detach();\n }\n this.autoDetect(this.view);\n }\n\n shouldDetach() {\n return this.treeMobxAutorun && this.treeMobxAutorun.detach;\n }\n\n autoDetect(view: EmbeddedViewRef<any>) {\n this.dispose = autorun(() => view.detectChanges());\n }\n\n ngOnDestroy() {\n if (this.dispose) {\n this.dispose();\n }\n }\n}\n","export const KEYS = {\n LEFT: 37,\n UP: 38,\n RIGHT: 39,\n DOWN: 40,\n ENTER: 13,\n SPACE: 32,\n CONTEXT_MENU: 32,\n};\n","import { TreeNode } from './tree-node.model';\nimport { TreeModel } from './tree.model';\nimport { KEYS } from '../constants/keys';\nimport { ITreeOptions } from '../defs/api';\n\nexport interface IActionHandler {\n (tree: TreeModel, node: TreeNode, $event: any, ...rest);\n}\n\nexport const TREE_ACTIONS = {\n TOGGLE_ACTIVE: (tree: TreeModel, node: TreeNode, $event: any) => node && node.toggleActivated(),\n TOGGLE_ACTIVE_MULTI: (tree: TreeModel, node: TreeNode, $event: any) => node && node.toggleActivated(true),\n TOGGLE_SELECTED: (tree: TreeModel, node: TreeNode, $event: any) => node && node.toggleSelected(),\n ACTIVATE: (tree: TreeModel, node: TreeNode, $event: any) => node.setIsActive(true),\n DEACTIVATE: (tree: TreeModel, node: TreeNode, $event: any) => node.setIsActive(false),\n SELECT: (tree: TreeModel, node: TreeNode, $event: any) => node.setIsSelected(true),\n DESELECT: (tree: TreeModel, node: TreeNode, $event: any) => node.setIsSelected(false),\n FOCUS: (tree: TreeModel, node: TreeNode, $event: any) => node.focus(),\n TOGGLE_EXPANDED: (tree: TreeModel, node: TreeNode, $event: any) => node.hasChildren && node.toggleExpanded(),\n EXPAND: (tree: TreeModel, node: TreeNode, $event: any) => node.expand(),\n COLLAPSE: (tree: TreeModel, node: TreeNode, $event: any) => node.collapse(),\n DRILL_DOWN: (tree: TreeModel, node: TreeNode, $event: any) => tree.focusDrillDown(),\n DRILL_UP: (tree: TreeModel, node: TreeNode, $event: any) => tree.focusDrillUp(),\n NEXT_NODE: (tree: TreeModel, node: TreeNode, $event: any) => tree.focusNextNode(),\n PREVIOUS_NODE: (tree: TreeModel, node: TreeNode, $event: any) => tree.focusPreviousNode(),\n MOVE_NODE: (tree: TreeModel, node: TreeNode, $event: any, { from, to }: { from: any; to: any }) => {\n // default action assumes from = node, to = {parent, index}\n if ($event.ctrlKey) {\n tree.copyNode(from, to);\n } else {\n tree.moveNode(from, to);\n }\n },\n};\n\nconst defaultActionMapping: IActionMapping = {\n mouse: {\n click: TREE_ACTIONS.TOGGLE_ACTIVE,\n dblClick: null,\n contextMenu: null,\n expanderClick: TREE_ACTIONS.TOGGLE_EXPANDED,\n checkboxClick: TREE_ACTIONS.TOGGLE_SELECTED,\n drop: TREE_ACTIONS.MOVE_NODE,\n },\n keys: {\n [KEYS.RIGHT]: TREE_ACTIONS.DRILL_DOWN,\n [KEYS.LEFT]: TREE_ACTIONS.DRILL_UP,\n [KEYS.DOWN]: TREE_ACTIONS.NEXT_NODE,\n [KEYS.UP]: TREE_ACTIONS.PREVIOUS_NODE,\n [KEYS.SPACE]: TREE_ACTIONS.TOGGLE_ACTIVE,\n [KEYS.ENTER]: TREE_ACTIONS.TOGGLE_ACTIVE,\n },\n};\n\nexport interface IActionMapping {\n mouse?: {\n click?: IActionHandler;\n dblClick?: IActionHandler;\n contextMenu?: IActionHandler;\n expanderClick?: IActionHandler;\n checkboxClick?: IActionHandler;\n dragStart?: IActionHandler;\n drag?: IActionHandler;\n dragEnd?: IActionHandler;\n dragOver?: IActionHandler;\n dragLeave?: IActionHandler;\n dragEnter?: IActionHandler;\n drop?: IActionHandler;\n mouseOver?: IActionHandler;\n mouseOut?: IActionHandler;\n };\n keys?: {\n [key: number]: IActionHandler;\n };\n}\n\nexport class TreeOptions {\n get hasChildrenField(): string {\n return this.options.hasChildrenField || 'hasChildren';\n }\n get childrenField(): string {\n return this.options.childrenField || 'children';\n }\n get displayField(): string {\n return this.options.displayField || 'name';\n }\n get idField(): string {\n return this.options.idField || 'id';\n }\n get isExpandedField(): string {\n return this.options.isExpandedField || 'isExpanded';\n }\n get getChildren(): any {\n return this.options.getChildren;\n }\n get levelPadding(): number {\n return this.options.levelPadding || 0;\n }\n get useVirtualScroll(): boolean {\n return this.options.useVirtualScroll;\n }\n get animateExpand(): boolean {\n return this.options.animateExpand;\n }\n get animateSpeed(): number {\n return this.options.animateSpeed || 1;\n }\n get animateAcceleration(): number {\n return this.options.animateAcceleration || 1.2;\n }\n get scrollOnActivate(): boolean {\n return this.options.scrollOnActivate === undefined ? true : this.options.scrollOnActivate;\n }\n get rtl(): boolean {\n return !!this.options.rtl;\n }\n get rootId(): any {\n return this.options.rootId;\n }\n get useCheckbox(): boolean {\n return this.options.useCheckbox;\n }\n get useTriState(): boolean {\n return this.options.useTriState === undefined ? true : this.options.useTriState;\n }\n get scrollContainer(): HTMLElement {\n return this.options.scrollContainer;\n }\n get allowDragoverStyling(): boolean {\n return this.options.allowDragoverStyling === undefined ? true : this.options.allowDragoverStyling;\n }\n actionMapping: IActionMapping;\n\n constructor(private options: ITreeOptions = {}) {\n this.actionMapping = {\n mouse: {\n click: this.options?.actionMapping?.mouse?.click ?? defaultActionMapping.mouse.click,\n dblClick: this.options?.actionMapping?.mouse?.dblClick ?? defaultActionMapping.mouse.dblClick,\n contextMenu: this.options?.actionMapping?.mouse?.contextMenu ?? defaultActionMapping.mouse.contextMenu,\n expanderClick: this.options?.actionMapping?.mouse?.expanderClick ?? defaultActionMapping.mouse.expanderClick,\n checkboxClick: this.options?.actionMapping?.mouse?.checkboxClick ?? defaultActionMapping.mouse.checkboxClick,\n drop: this.options?.actionMapping?.mouse?.drop ?? defaultActionMapping.mouse.drop,\n dragStart: this.options?.actionMapping?.mouse?.dragStart ?? undefined,\n drag: this.options?.actionMapping?.mouse?.drag ?? undefined,\n dragEnd: this.options?.actionMapping?.mouse?.dragEnd ?? undefined,\n dragOver: this.options?.actionMapping?.mouse?.dragOver ?? undefined,\n dragLeave: this.options?.actionMapping?.mouse?.dragLeave ?? undefined,\n dragEnter: this.options?.actionMapping?.mouse?.dragEnter ?? undefined,\n mouseOver: this.options?.actionMapping?.mouse?.mouseOver ?? undefined,\n mouseOut: this.options?.actionMapping?.mouse?.mouseOut ?? undefined,\n },\n keys: {\n [KEYS.RIGHT]: TREE_ACTIONS.DRILL_DOWN,\n [KEYS.LEFT]: TREE_ACTIONS.DRILL_UP,\n [KEYS.DOWN]: TREE_ACTIONS.NEXT_NODE,\n [KEYS.UP]: TREE_ACTIONS.PREVIOUS_NODE,\n [KEYS.SPACE]: TREE_ACTIONS.TOGGLE_ACTIVE,\n [KEYS.ENTER]: TREE_ACTIONS.TOGGLE_ACTIVE,\n },\n };\n\n if (this.options?.actionMapping?.keys) {\n this.actionMapping.keys = {\n ...this.actionMapping.keys,\n ...this.options.actionMapping.keys,\n };\n }\n\n if (options.rtl) {\n this.actionMapping.keys[KEYS.RIGHT] =\n <IActionHandler>options.actionMapping?.keys[KEYS.RIGHT] || TREE_ACTIONS.DRILL_UP;\n this.actionMapping.keys[KEYS.LEFT] =\n <IActionHandler>options.actionMapping?.keys[KEYS.LEFT] || TREE_ACTIONS.DRILL_DOWN;\n }\n }\n\n getNodeClone(node: TreeNode): any {\n if (this.options.getNodeClone) {\n return this.options.getNodeClone(node);\n }\n\n // remove id from clone\n // keeping ie11 compatibility\n const nodeClone = Object.assign({}, node.data);\n if (nodeClone.id) {\n delete nodeClone.id;\n }\n return nodeClone;\n }\n\n allowDrop(element, to, $event?): boolean {\n if (this.options.allowDrop instanceof Function) {\n return this.options.allowDrop(element, to, $event);\n } else {\n return this.options.allowDrop === undefined ? true : this.options.allowDrop;\n }\n }\n\n allowDrag(node: TreeNode): boolean {\n if (this.options.allowDrag instanceof Function) {\n return this.options.allowDrag(node);\n } else {\n return this.options.allowDrag;\n }\n }\n\n nodeClass(node: TreeNode): string {\n return this.options.nodeClass ? this.options.nodeClass(node) : '';\n }\n\n nodeHeight(node: TreeNode): number {\n if (node.data.virtual) {\n return 0;\n }\n\n let nodeHeight = this.options.nodeHeight || 22;\n\n if (typeof nodeHeight === 'function') {\n nodeHeight = nodeHeight(node);\n }\n\n // account for drop slots:\n return nodeHeight + (node.index === 0 ? 2 : 1) * this.dropSlotHeight;\n }\n\n get dropSlotHeight(): number {\n return typeof this.options.dropSlotHeight === 'number' ? this.options.dropSlotHeight : 2;\n }\n}\n","export const TREE_EVENTS = {\n toggleExpanded: 'toggleExpanded',\n activate: 'activate',\n deactivate: 'deactivate',\n nodeActivate: 'nodeActivate',\n nodeDeactivate: 'nodeDeactivate',\n select: 'select',\n deselect: 'deselect',\n focus: 'focus',\n blur: 'blur',\n initialized: 'initialized',\n updateData: 'updateData',\n moveNode: 'moveNode',\n copyNode: 'copyNode',\n event: 'event',\n loadNodeChildren: 'loadNodeChildren',\n changeFilter: 'changeFilter',\n stateChange: 'stateChange',\n};\n","import { observable, computed, reaction, autorun, action, IReactionDisposer } from 'mobx';\nimport { TreeModel } from './tree.model';\nimport { TreeOptions } from './tree-options.model';\nimport { ITreeNode } from '../defs/api';\nimport { TREE_EVENTS } from '../constants/events';\n\nexport class TreeNode implements ITreeNode {\n private handler: IReactionDisposer;\n @computed get isHidden() {\n return this.treeModel.isHidden(this);\n }\n @computed get isExpanded() {\n return this.treeModel.isExpanded(this);\n }\n @computed get isActive() {\n return this.treeModel.isActive(this);\n }\n @computed get isFocused() {\n return this.treeModel.isNodeFocused(this);\n }\n @computed get isSelected() {\n if (this.isSelectable()) {\n return this.treeModel.isSelected(this);\n } else {\n return this.children.some((node: TreeNode) => node.isSelected);\n }\n }\n @computed get isAllSelected() {\n if (this.isSelectable()) {\n return this.treeModel.isSelected(this);\n } else {\n return this.children.every((node: TreeNode) => node.isAllSelected);\n }\n }\n @computed get isPartiallySelected() {\n return this.isSelected && !this.isAllSelected;\n }\n\n @observable children: TreeNode[];\n @observable index: number;\n @observable position = 0;\n @observable height: number;\n @computed get level(): number {\n return this.parent ? this.parent.level + 1 : 0;\n }\n @computed get path(): string[] {\n return this.parent ? [...this.parent.path, this.id] : [];\n }\n\n get elementRef(): any {\n throw `Element Ref is no longer supported since introducing virtual scroll\\n\n You may use a template to obtain a reference to the element`;\n }\n\n private _originalNode: any;\n get originalNode() {\n return this._originalNode;\n }\n\n constructor(\n public data: any,\n public parent: TreeNode,\n public treeModel: TreeModel,\n index: number,\n ) {\n if (this.id === undefined || this.id === null) {\n this.id = uuid();\n } // Make sure there's a unique id without overriding existing ids to work with immutable data structures\n this.index = index;\n\n if (this.getField('children')) {\n this._initChildren();\n }\n this.autoLoadChildren();\n }\n\n // helper get functions:\n get hasChildren(): boolean {\n return !!(this.getField('hasChildren') || (this.children && this.children.length > 0));\n }\n get isCollapsed(): boolean {\n return !this.isExpanded;\n }\n get isLeaf(): boolean {\n return !this.hasChildren;\n }\n get isRoot(): boolean {\n return this.parent.data.virtual;\n }\n get realParent(): TreeNode {\n return this.isRoot ? null : this.parent;\n }\n\n // proxy functions:\n get options(): TreeOptions {\n return this.treeModel.options;\n }\n fireEvent(event) {\n this.treeModel.fireEvent(event);\n }\n\n // field accessors:\n get displayField() {\n return this.getField('display');\n }\n\n get id() {\n return this.getField('id');\n }\n\n set id(value) {\n this.setField('id', value);\n }\n\n getField(key) {\n return this.data[this.options[`${key}Field`]];\n }\n\n setField(key, value) {\n this.data[this.options[`${key}Field`]] = value;\n }\n\n // traversing:\n _findAdjacentSibling(steps, skipHidden = false) {\n const siblings = this._getParentsChildren(skipHidden);\n const index = siblings.indexOf(this);\n\n return siblings.length > index + steps ? siblings[index + steps] : null;\n }\n\n findNextSibling(skipHidden = false) {\n return this._findAdjacentSibling(+1, skipHidden);\n }\n\n findPreviousSibling(skipHidden = false) {\n return this._findAdjacentSibling(-1, skipHidden);\n }\n\n getVisibleChildren() {\n return this.visibleChildren;\n }\n\n @computed get visibleChildren() {\n return (this.children || []).filter((node) => !node.isHidden);\n }\n\n getFirstChild(skipHidden = false) {\n let children = skipHidden ? this.visibleChildren : this.children;\n\n return children != null && children.length ? children[0] : null;\n }\n\n getLastChild(skipHidden = false) {\n let children = skipHidden ? this.visibleChildren : this.children;\n\n return children != null && children.length ? children[children.length - 1] : null;\n }\n\n findNextNode(goInside = true, skipHidden = false) {\n return (\n (goInside && this.isExpanded && this.getFirstChild(skipHidden)) ||\n this.findNextSibling(skipHidden) ||\n (this.parent && this.parent.findNextNode(false, skipHidden))\n );\n }\n\n findPreviousNode(skipHidden = false) {\n let previousSibling = this.findPreviousSibling(skipHidden);\n if (!previousSibling) {\n return this.realParent;\n }\n return previousSibling._getLastOpenDescendant(skipHidden);\n }\n\n _getLastOpenDescendant(skipHidden = false) {\n const lastChild = this.getLastChild(skipHidden);\n return this.isCollapsed || !lastChild ? this : lastChild._getLastOpenDescendant(skipHidden);\n }\n\n private _getParentsChildren(skipHidden = false): any[] {\n const children = this.parent && (skipHidden ? this.parent.getVisibleChildren() : this.parent.children);\n\n return children || [];\n }\n\n private getIndexInParent(skipHidden = false) {\n return this._getParentsChildren(skipHidden).indexOf(this);\n }\n\n isDescendantOf(node: TreeNode) {\n if (this === node) return true;\n else return this.parent && this.parent.isDescendantOf(node);\n }\n\n getNodePadding(): string {\n return this.options.levelPadding * (this.level - 1) + 'px';\n }\n\n getClass(): string {\n return [this.options.nodeClass(this), `tree-node-level-${this.level}`].join(' ');\n }\n\n onDrop($event) {\n this.mouseAction('drop', $event.event, {\n from: $event.element,\n to: { parent: this, index: 0, dropOnNode: true },\n });\n }\n\n allowDrop = (element, $event?) => {\n return this.options.allowDrop(element, { parent: this, index: 0 }, $event);\n };\n\n allowDragoverStyling = () => {\n return this.options.allowDragoverStyling;\n };\n\n allowDrag() {\n return this.options.allowDrag(this);\n }\n\n // helper methods:\n loadNodeChildren() {\n if (!this.options.getChildren) {\n return Promise.resolve(); // Not getChildren method - for using redux\n }\n return Promise.resolve(this.options.getChildren(this))\n .then((children) => {\n if (children) {\n this.setField('children', children);\n this._initChildren();\n if (this.options.useTriState && this.treeModel.isSelected(this)) {\n this.setIsSelected(true);\n }\n this.children.forEach((child) => {\n if (child.getField('isExpanded') && child.hasChildren) {\n child.expand();\n }\n });\n }\n })\n .then(() => {\n this.fireEvent({\n eventName: TREE_EVENTS.loadNodeChildren,\n node: this,\n });\n });\n }\n\n expand() {\n if (!this.isExpanded) {\n this.toggleExpanded();\n }\n\n return this;\n }\n\n collapse() {\n if (this.isExpanded) {\n this.toggleExpanded();\n }\n\n return this;\n }\n\n doForAll(fn: (node: ITreeNode) => any) {\n Promise.resolve(fn(this)).then(() => {\n if (this.children) {\n this.children.forEach((child) => child.doForAll(fn));\n }\n });\n }\n\n expandAll() {\n this.doForAll((node) => node.expand());\n }\n\n collapseAll() {\n this.doForAll((node) => node.collapse());\n }\n\n ensureVisible() {\n if (this.realParent) {\n this.realParent.expand();\n this.realParent.ensureVisible();\n }\n\n return this;\n }\n\n toggleExpanded() {\n this.setIsExpanded(!this.isExpanded);\n\n return this;\n }\n\n setIsExpanded(value) {\n if (this.hasChildren) {\n this.treeModel.setExpandedNode(this, value);\n }\n\n return this;\n }\n\n autoLoadChildren() {\n this.handler = reaction(\n () => this.isExpanded,\n (isExpanded) => {\n if (!this.children && this.hasChildren && isExpanded) {\n this.loadNodeChildren();\n }\n },\n { fireImmediately: true },\n );\n }\n\n dispose() {\n if (this.children) {\n this.children.forEach((child) => child.dispose());\n }\n if (this.handler) {\n this.handler();\n }\n this.parent = null;\n this.children = null;\n }\n\n setIsActive(value, multi = false) {\n this.treeModel.setActiveNode(this, value, multi);\n if (value) {\n this.focus(this.options.scrollOnActivate);\n }\n\n return this;\n }\n\n isSelectable() {\n return this.isLeaf || !this.children || !this.options.useTriState;\n }\n\n @action setIsSelected(value) {\n if (this.isSelectable()) {\n this.treeModel.setSelectedNode(this, value);\n } else {\n this.visibleChildren.forEach((child) => child.setIsSelected(value));\n }\n\n return this;\n }\n\n toggleSelected() {\n this.setIsSelected(!this.isSelected);\n\n return this;\n }\n\n toggleActivated(multi = false) {\n this.setIsActive(!this.isActive, multi);\n\n return this;\n }\n\n setActiveAndVisible(multi = false) {\n this.setIsActive(true, multi).ensureVisible();\n\n setTimeout(this.scrollIntoView.bind(this));\n\n return this;\n }\n\n scrollIntoView(force = false) {\n this.treeModel.virtualScroll.scrollIntoView(this, force);\n }\n\n focus(scroll = true) {\n let previousNode = this.treeModel.getFocusedNode();\n this.treeModel.setFocusedNode(this);\n if (scroll) {\n this.scrollIntoView();\n }\n if (previousNode) {\n this.fireEvent({ eventName: TREE_EVENTS.blur, node: previousNode });\n }\n this.fireEvent({ eventName: TREE_EVENTS.focus, node: this });\n\n return this;\n }\n\n blur() {\n let previousNode = this.treeModel.getFocusedNode();\n this.treeModel.setFocusedNode(null);\n if (previousNode) {\n this.fireEvent({ eventName: TREE_EVENTS.blur, node: this });\n }\n\n return this;\n }\n\n setIsHidden(value) {\n this.treeModel.setIsHidden(this, value);\n }\n\n hide() {\n this.setIsHidden(true);\n }\n\n show() {\n this.setIsHidden(false);\n }\n\n mouseAction(actionName: string, $event, data: any = null) {\n this.treeModel.setFocus(true);\n\n const actionMapping = this.options.actionMapping.mouse;\n const mouseAction = actionMapping[actionName];\n\n if (mouseAction) {\n mouseAction(this.treeModel, this, $event, data);\n }\n }\n\n getSelfHeight() {\n return this.options.nodeHeight(this);\n }\n\n @action _initChildren() {\n this.children = this.getField('children').map((c, index) => new TreeNode(c, this, this.treeModel, index));\n }\n}\n\nfunction uuid() {\n return Math.floor(Math.random() * 10000000000000);\n}\n","import { Injectable, OnDestroy } from '@angular/core';\nimport { observable, computed, action, autorun } from 'mobx';\nimport { Subscription } from 'rxjs';\nimport { TreeNode } from './tree-node.model';\nimport { TreeOptions } from './tree-options.model';\nimport { TreeVirtualScroll } from './tree-virtual-scroll.model';\nimport { ITreeModel, IDType, IDTypeDictionary } from '../defs/api';\nimport { TREE_EVENTS } from '../constants/events';\n\n@Injectable()\nexport class TreeModel implements ITreeModel, OnDestroy {\n static focusedTree = null;\n\n options: TreeOptions = new TreeOptions();\n nodes: any[];\n eventNames = Object.keys(TREE_EVENTS);\n virtualScroll: TreeVirtualScroll;\n\n @observable roots: TreeNode[];\n @observable expandedNodeIds: IDTypeDictionary = {};\n @observable selectedLeafNodeIds: IDTypeDictionary = {};\n @observable activeNodeIds: IDTypeDictionary = {};\n @observable hiddenNodeIds: IDTypeDictionary = {};\n @observable focusedNodeId: IDType = null;\n @observable virtualRoot: TreeNode;\n\n private firstUpdate = true;\n private events: any;\n private subscriptions: Subscription[] = [];\n\n // events\n fireEvent(event) {\n event.treeModel = this;\n this.events[event.eventName].emit(event);\n this.events.event.emit(event);\n }\n\n subscribe(eventName, fn) {\n const subscription = this.events[eventName].subscribe(fn);\n this.subscriptions.push(subscription);\n }\n\n // getters\n getFocusedNode(): TreeNode {\n return this.focusedNode;\n }\n\n getActiveNode(): TreeNode {\n return this.activeNodes[0];\n }\n\n getActiveNodes(): TreeNode[] {\n return this.activeNodes;\n }\n\n getVisibleRoots() {\n return this.virtualRoot.visibleChildren;\n }\n\n getFirstRoot(skipHidden = false) {\n const root = skipHidden ? this.getVisibleRoots() : this.roots;\n return root != null && root.length ? root[0] : null;\n }\n\n getLastRoot(skipHidden = false) {\n const root = skipHidden ? this.getVisibleRoots() : this.roots;\n return root != null && root.length ? root[root.length - 1] : null;\n }\n\n get isFocused() {\n return TreeModel.focusedTree === this;\n }\n\n isNodeFocused(node) {\n return this.focusedNode === node;\n }\n\n isEmptyTree(): boolean {\n return this.roots && this.roots.length === 0;\n }\n\n @computed get focusedNode() {\n return this.focusedNodeId ? this.getNodeById(this.focusedNodeId) : null;\n }\n\n @computed get expandedNodes() {\n const nodes = Object.keys(this.expandedNodeIds)\n .filter((id) => this.expandedNodeIds[id])\n .map((id) => this.getNodeById(id));\n\n return nodes.filter(Boolean);\n }\n\n @computed get activeNodes() {\n const nodes = Object.keys(this.activeNodeIds)\n .filter((id) => this.activeNodeIds[id])\n .map((id) => this.getNodeById(id));\n\n return nodes.filter(Boolean);\n }\n\n @computed get hiddenNodes() {\n const nodes = Object.keys(this.hiddenNodeIds)\n .filter((id) => this.hiddenNodeIds[id])\n .map((id) => this.getNodeById(id));\n\n return nodes.filter(Boolean);\n }\n\n @computed get selectedLeafNodes() {\n const nodes = Object.keys(this.selectedLeafNodeIds)\n .filter((id) => this.selectedLeafNodeIds[id])\n .map((id) => this.getNodeById(id));\n\n return nodes.filter(Boolean);\n }\n\n // locating nodes\n getNodeByPath(path: any[], startNode = null): TreeNode {\n if (!path) return null;\n\n startNode = startNode || this.virtualRoot;\n if (path.length === 0) return startNode;\n\n if (!startNode.children) return null;\n\n const childId = path.shift();\n const childNode = startNode.children.find((c) => c.id === childId);\n\n if (!childNode) return null;\n\n return this.getNodeByPath(path, childNode);\n }\n\n getNodeById(id) {\n const idStr = id.toString();\n\n return this.getNodeBy((node) => node.id.toString() === idStr);\n }\n\n getNodeBy(predicate, startNode = null) {\n startNode = startNode || this.virtualRoot;\n\n if (!startNode.children) return null;\n\n const found = startNode.children.find(predicate);\n\n if (found) {\n // found in children\n return found;\n } else {\n // look in children's children\n for (let child of startNode.children) {\n const foundInChildren = this.getNodeBy(predicate, child);\n if (foundInChildren) return foundInChildren;\n }\n }\n }\n\n isExpanded(node) {\n return this.expandedNodeIds[node.id];\n }\n\n isHidden(node) {\n return this.hiddenNodeIds[node.id];\n }\n\n isActive(node) {\n return this.activeNodeIds[node.id];\n }\n\n isSelected(node) {\n return this.selectedLeafNodeIds[node.id];\n }\n\n ngOnDestroy() {\n this.dispose();\n this.unsubscribeAll();\n }\n\n dispose() {\n // Dispose reactions of the replaced nodes\n if (this.virtualRoot) {\n this.virtualRoot.dispose();\n }\n }\n\n unsubscribeAll() {\n this.subscriptions.forEach((subscription) => subscription.unsubscribe());\n this.subscriptions = [];\n }\n\n // actions\n @action setData({ nodes, options = null, events = null }: { nodes: any; options: any; events: any }) {\n if (options) {\n this.options = new TreeOptions(options);\n }\n if (events) {\n this.events = events;\n }\n if (nodes) {\n this.nodes = nodes;\n }\n\n this.update();\n }\n\n @action update() {\n // Rebuild tree:\n let virtualRootConfig = {\n id: this.options.rootId,\n virtual: true,\n [this.options.childrenField]: this.nodes,\n };\n\n this.dispose();\n\n this.virtualRoot = new TreeNode(virtualRootConfig, null, this, 0);\n\n this.roots = this.virtualRoot.children;\n\n // Fire event:\n if (this.firstUpdate) {\n if (this.roots) {\n this.firstUpdate = false;\n this._calculateExpandedNodes();\n }\n } else {\n this.fireEvent({ eventName: TREE_EVENTS.updateData });\n }\n }\n\n @action setFocusedNode(node) {\n this.focusedNodeId = node ? node.id : null;\n }\n\n @action setFocus(value) {\n TreeModel.focusedTree = value ? this : null;\n }\n\n @action doForAll(fn) {\n this.roots.forEach((root) => root.doForAll(fn));\n }\n\n @action focusNextNode() {\n let previousNode = this.getFocusedNode();\n let nextNode = previousNode ? previousNode.findNextNode(true, true) : this.getFirstRoot(true);\n if (nextNode) nextNode.focus();\n }\n\n @action focusPreviousNode() {\n let previousNode = this.getFocusedNode();\n let nextNode = previousNode ? previousNode.findPreviousNode(true) : this.getLastRoot(true);\n if (nextNode) nextNode.focus();\n }\n\n @action focusDrillDown() {\n let previousNode = this.getFocusedNode();\n if (previousNode && previousNode.isCollapsed && previousNode.hasChildren) {\n previousNode.toggleExpanded();\n } else {\n let nextNode = previousNode ? previousNode.getFirstChild(true) : this.getFirstRoot(true);\n if (nextNode) nextNode.focus();\n }\n }\n\n @action focusDrillUp() {\n let previousNode = this.getFocusedNode();\n if (!previousNode) return;\n if (previousNode.isExpanded) {\n previousNode.toggleExpanded();\n } else {\n let nextNode = previousNode.realParent;\n if (nextNode) nextNode.focus();\n }\n }\n\n @action setActiveNode(node, value, multi = false) {\n if (multi) {\n this._setActiveNodeMulti(node, value);\n } else {\n this._setActiveNodeSingle(node, value);\n }\n\n if (value) {\n node.focus(this.options.scrollOnActivate);\n this.fireEvent({ eventName: TREE_EVENTS.activate, node });\n this.fireEvent({ eventName: TREE_EVENTS.nodeActivate, node }); // For IE11\n } else {\n this.fireEvent({ eventName: TREE_EVENTS.deactivate, node });\n this.fireEvent({ eventName: TREE_EVENTS.nodeDeactivate, node }); // For IE11\n }\n }\n\n @action setSelectedNode(node, value) {\n this.selectedLeafNodeIds = Object.assign({}, this.selectedLeafNodeIds, {\n [node.id]: value,\n });\n\n if (value) {\n node.focus();\n this.fireEvent({ eventName: TREE_EVENTS.select, node });\n } else {\n this.fireEvent({ eventName: TREE_EVENTS.deselect, node });\n }\n }\n\n @action setExpandedNode(node, value) {\n this.expandedNodeIds = Object.assign({}, this.expandedNodeIds, {\n [node.id]: value,\n });\n this.fireEvent({\n eventName: TREE_EVENTS.toggleExpanded,\n node,\n isExpanded: value,\n });\n }\n\n @action expandAll() {\n this.roots.forEach((root) => root.expandAll());\n }\n\n @action collapseAll() {\n this.roots.forEach((root) => root.collapseAll());\n }\n\n @action setIsHidden(node, value) {\n this.hiddenNodeIds = Object.assign({}, this.hiddenNodeIds, {\n [node.id]: value,\n });\n }\n\n @action setHiddenNodeIds(nodeIds) {\n this.hiddenNodeIds = nodeIds.reduce(\n (hiddenNodeIds, id) =>\n Object.assign(hiddenNodeIds, {\n [id]: true,\n }),\n {},\n );\n }\n\n performKeyAction(node, $event) {\n const keyAction = this.options.actionMapping.keys[$event.keyCode];\n if (keyAction) {\n $event.preventDefault();\n keyAction(this, node, $event);\n return true;\n } else {\n return false;\n }\n }\n\n @action filterNodes(filter, autoShow = true) {\n let filterFn;\n\n if (!filter) {\n return this.clearFilter();\n }\n\n // support function and string filter\n if (filter && typeof filter.valueOf() === 'string') {\n filterFn = (node) => node.displayField.toLowerCase().indexOf(filter.toLowerCase()) !== -1;\n } else if (filter && typeof filter === 'function') {\n filterFn = filter;\n } else {\n console.error(\"Don't know what to do with filter\", filter);\n console.error('Should be either a string or function');\n return;\n }\n\n const ids = {};\n this.roots.forEach((node) => this._filterNode(ids, node, filterFn, autoShow));\n this.hiddenNodeIds = ids;\n this.fireEvent({ eventName: TREE_EVENTS.changeFilter });\n }\n\n @action clearFilter() {\n this.hiddenNodeIds = {};\n this.fireEvent({ eventName: TREE_EVENTS.changeFilter });\n }\n\n @action moveNode(node, to) {\n const fromIndex = node.getIndexInParent();\n const fromParent = node.parent;\n\n if (!this.canMoveNode(node, to, fromIndex)) return;\n\n const fromChildren = fromParent.getField('children');\n\n // If node doesn't have children - create children array\n if (!to.parent.getField('children')) {\n to.parent.setField('children', []);\n }\n const toChildren = to.parent.getField('children');\n\n const originalNode = fromChildren.splice(fromIndex, 1)[0];\n\n // Compensate for index if already removed from parent:\n let toIndex = fromParent === to.parent && to.index > fromIndex ? to.index - 1 : to.index;\n\n toChildren.splice(toIndex, 0, originalNode);\n\n fromParent.treeModel.update();\n if (to.parent.treeModel !== fromParent.treeModel) {\n to.parent.treeModel.update();\n }\n\n this.fireEvent({\n eventName: TREE_EVENTS.moveNode,\n node: originalNode,\n to: { parent: to.parent.data, index: toIndex },\n from: { parent: fromParent.data, index: fromIndex },\n });\n }\n\n @action copyNode(node, to) {\n const fromIndex = node.getIndexInParent();\n\n if (!this.canMoveNode(node, to, fromIndex)) return;\n\n // If node doesn't have children - create children array\n if (!to.parent.getField('children')) {\n to.parent.setField('children', []);\n }\n const toChildren = to.parent.getField('children');\n\n const nodeCopy = this.options.getNodeClone(node);\n\n toChildren.splice(to.index, 0, nodeCopy);\n\n node.treeModel.update();\n if (to.parent.treeModel !== node.treeModel) {\n to.parent.treeModel.update();\n }\n\n this.fireEvent({\n eventName: TREE_EVENTS.copyNode,\n node: nodeCopy,\n to: { parent: to.parent.data, index: to.index },\n });\n }\n\n getState() {\n return {\n expandedNodeIds: this.expandedNodeIds,\n selectedLeafNodeIds: this.selectedLeafNodeIds,\n activeNodeIds: this.activeNodeIds,\n hiddenNodeIds: this.hiddenNodeIds,\n focusedNodeId: this.focusedNodeId,\n };\n }\n\n @action setState(state) {\n if (!state) return;\n\n Object.assign(this, {\n expandedNodeIds: state.expandedNodeIds || {},\n selectedLeafNodeIds: state.selectedLeafNodeIds || {},\n activeNodeIds: state.activeNodeIds || {},\n hiddenNodeIds: state.hiddenNodeIds || {},\n focusedNodeId: state.focusedNodeId,\n });\n }\n\n subscribeToState(fn) {\n autorun(() => fn(this.getState()));\n }\n\n canMoveNode(node, to, fromIndex = undefined) {\n const fromNodeIndex = fromIndex || node.getIndexInParent();\n\n // same node:\n if (node.parent === to.parent && fromIndex === to.index) {\n return false;\n }\n\n return !to.parent.isDescendantOf(node);\n }\n\n calculateExpandedNodes() {\n this._calculateExpandedNodes();\n }\n\n // private methods\n private _filterNode(ids, node, filterFn, autoShow) {\n // if node passes function then it's visible\n let isVisible = filterFn(node);\n\n if (node.children) {\n // if one of node's children passes filter then this node is also visible\n node.children.forEach((child) => {\n if (this._filterNode(ids, child, filterFn, autoShow)) {\n isVisible = true;\n }\n });\n }\n\n // mark node as hidden\n if (!isVisible) {\n ids[node.id] = true;\n }\n // auto expand parents to make sure the filtered nodes are visible\n if (autoShow && isVisible) {\n node.ensureVisible();\n }\n return isVisible;\n }\n\n private _calculateExpandedNodes(startNode = null) {\n startNode = startNode || this.virtualRoot;\n\n if (startNode.data[this.options.isExpandedField]) {\n this.expandedNodeIds = Object.assign({}, this.expandedNodeIds, {\n [startNode.id]: true,\n });\n }\n if (startNode.children) {\n startNode.children.forEach((child) => this._calculateExpandedNodes(child));\n }\n }\n\n private _setActiveNodeSingle(node, value) {\n // Deactivate all other nodes:\n this.activeNodes\n .filter((activeNode) => activeNode !== node)\n .forEach((activeNode) => {\n this.fireEvent({ eventName: TREE_EVENTS.deactivate, node: activeNode });\n this.fireEvent({\n eventName: TREE_EVENTS.nodeDeactivate,\n node: activeNode,\n }); // For IE11\n });\n\n if (value) {\n this.activeNodeIds = { [node.id]: true };\n } else {\n this.activeNodeIds = {};\n }\n }\n\n private _setActiveNodeMulti(node, value) {\n this.activeNodeIds = Object.assign({}, this.activeNodeIds, {\n [node.id]: value,\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class TreeDraggedElement {\n _draggedElement: any = null;\n\n set(draggedElement: any) {\n this._draggedElement = draggedElement;\n }\n\n get(): any {\n return this._draggedElement;\n }\n\n isDragging() {\n return !!this.get();\n }\n}\n","import { Injectable } from '@angular/core';\nimport { observable, computed, action, autorun, reaction } from 'mobx';\nimport { TreeModel } from './tree.model';\nimport { TREE_EVENTS } from '../constants/events';\n\nconst Y_OFFSET = 500; // Extra pixels outside the viewport, in each direction, to render nodes in\nconst Y_EPSILON = 150; // Minimum pixel change required to recalculate the rendered nodes\n\n@Injectable()\nexport class TreeVirtualScroll {\n private _dispose: any;\n\n @observable yBlocks = 0;\n @observable x = 0;\n @observable viewportHeight = null;\n viewport = null;\n\n @computed get y() {\n return this.yBlocks * Y_EPSILON;\n }\n\n @computed get totalHeight() {\n return this.treeModel.virtualRoot ? this.treeModel.virtualRoot.height : 0;\n }\n\n constructor(private treeModel: TreeModel) {\n treeModel.virtualScroll = this;\n this._dispose = [autorun(() => this.fixScroll())];\n }\n\n fireEvent(event) {\n this.treeModel.fireEvent(event);\n }\n\n init() {\n const fn = this.recalcPositions.bind(this);\n\n fn();\n this._dispose = [\n ...this._dispose,\n reaction(() => this.treeModel.roots, fn),\n reaction(() => this.treeModel.expandedNodeIds, fn),\n reaction(() => this.treeModel.hiddenNodeIds, fn),\n ];\n this.treeModel.subscribe(TREE_EVENTS.loadNodeChildren, fn);\n }\n\n isEnabled() {\n return this.treeModel.options.useVirtualScroll;\n }\n\n @action private _setYBlocks(value) {\n this.yBlocks = value;\n }\n\n @action recalcPositions() {\n this.treeModel.virtualRoot.height = this._getPositionAfter(this.treeModel.getVisibleRoots(), 0);\n }\n\n private _getPositionAfter(nodes, startPos) {\n let position = startPos;\n\n nodes.forEach((node) => {\n node.position = position;\n position = this._getPositionAfterNode(node, position);\n });\n return position;\n }\n\n private _getPositionAfterNode(node, startPos) {\n let position = node.getSelfHeight() + startPos;\n\n if (node.children && node.isExpanded) {\n // TBD: consider loading component as well\n position = this._getPositionAfter(node.visibleChildren, position);\n }\n node.height = position - startPos;\n return position;\n }\n\n clear() {\n this._dispose.forEach((d) => d());\n }\n\n @action setViewport(viewport) {\n Object.assign(this, {\n viewport,\n x: viewport.scrollLeft,\n yBlocks: Math.round(viewport.scrollTop / Y_EPSILON),\n viewportHeight: viewport.getBoundingClientRect ? viewport.getBoundingClientRect().height : 0,\n });\n }\n\n @action scrollIntoView(node, force, scrollToMiddle = true) {\n if (node.options.scrollContainer) {\n const scrollContainer = node.options.scrollContainer;\n const scrollContainerHeight = scrollContainer.getBoundingClientRect().height;\n const scrollContainerTop = scrollContainer.getBoundingClientRect().top;\n const nodeTop = this.viewport.getBoundingClientRect().top + node.position - scrollContainerTop;\n\n if (\n force || // force scroll to node\n nodeTop < scrollContainer.scrollTop || // node is above scroll container\n nodeTop + node.getSelfHeight() > scrollContainer.scrollTop + scrollContainerHeight\n ) {\n // node is below container\n scrollContainer.scrollTop = scrollToMiddle\n ? nodeTop - scrollContainerHeight / 2 // scroll to middle\n : nodeTop; // scroll to start\n }\n } else {\n if (\n force || // force scroll to node\n node.position < this.y || // node is above viewport\n node.position + node.getSelfHeight() > this.y + this.viewportHeight\n ) {\n // node is below viewport\n if (this.viewport) {\n this.viewport.scrollTop = scrollToMiddle\n ? node.position - this.viewportHeight / 2 // scroll to middle\n : node.position; // scroll to start\n\n this._setYBlocks(Math.floor(this.viewport.scrollTop / Y_EPSILON));\n }\n }\n }\n }\n\n getViewportNodes(nodes) {\n if (!nodes) return [];\n\n const visibleNodes = nodes.filter((node) => !node.isHidden);\n\n if (!this.isEnabled()) return visibleNodes;\n\n if (!this.viewportHeight || !visibleNodes.length) return [];\n\n // When loading children async this method is called before their height and position is calculated.\n // In that case firstIndex === 0 and lastIndex === visibleNodes.length - 1 (e.g. 1000),\n // which means that it loops through every visibleNodes item and push them into viewportNodes array.\n // We can prevent nodes from being pushed to the array and wait for the appropriate calculations to take place\n const lastVisibleNode = visibleNodes.slice(-1)[0];\n if (!lastVisibleNode.height && lastVisibleNode.position === 0) return [];\n\n // Search for first node in the viewport using binary search\n // Look for first node that starts after the beginning of the viewport (with buffer)\n // Or that ends after the beginning of the viewport\n const firstIndex = binarySearch(visibleNodes, (node) => {\n return node.position + Y_OFFSET > this.y || node.position + node.height > this.y;\n });\n\n // Search for last node in the viewport using binary search\n // Look for first node that starts after the end of the viewport (with buffer)\n const lastIndex = binarySearch(\n visibleNodes,\n (node) => {\n return node.position - Y_OFFSET > this.y + this.viewportHeight;\n },\n firstIndex,\n );\n\n const viewportNodes = [];\n\n for (let i = firstIndex; i <= lastIndex; i++) {\n viewportNodes.push(visibleNodes[i]);\n }\n\n return viewportNodes;\n }\n\n fixScroll() {\n const maxY = Math.max(0, this.totalHeight - this.viewportHeight);\n\n if (this.y < 0) this._setYBlocks(0);\n if (this.y > maxY) this._setYBlocks(maxY / Y_EPSILON);\n }\n}\n\nfunction binarySearch(nodes, condition, firstIndex = 0) {\n let index = firstIndex;\n let toIndex = nodes.length - 1;\n\n while (index !== toIndex) {\n let midIndex = Math.floor((index + toIndex) / 2);\n\n if (condition(nodes[midIndex])) {\n toIndex = midIndex;\n } else {\n if (index === midIndex) index = toIndex;\n else index = midIndex;\n }\n }\n return index;\n}\n","import { Component, Input, TemplateRef, ViewEncapsulation } from '@angular/core';\nimport { TreeNode } from '../models/tree-node.model';\n\n@Component({\n encapsulation: ViewEncapsulation.None,\n selector: 'tree-loading-component',\n template: `\n <span *ngIf=\"!template\">loading...</span>\n <ng-container [ngTemplateOutlet]=\"template\" [ngTemplateOutletContext]=\"{ $implicit: node }\"> </ng-container>\n `,\n standalone: false,\n})\nexport class LoadingComponent {\n @Input() template: TemplateRef<any>;\n @Input() node: TreeNode;\n}\n","import {\n AfterViewInit,\n Directive,\n ElementRef,\n EventEmitter,\n HostListener,\n Input,\n NgZone,\n OnDestroy,\n Output,\n Renderer2,\n} from '@angular/core';\nimport { TreeDraggedElement } from '../models/tree-dragged-element.model';\n\nconst DRAG_OVER_CLASS = 'is-dragging-over';\nconst DRAG_DISABLED_CLASS = 'is-dragging-over-disabled';\n\n@Directive({\n selector: '[treeDrop]',\n standalone: false,\n})\nexport class TreeDropDirective implements AfterViewInit, OnDestroy {\n @Input() allowDragoverStyling = true;\n @Output('treeDrop') onDropCallback = new EventEmitter();\n @Output('treeDropDragOver') onDragOverCallback = new EventEmitter();\n @Output('treeDropDragLeave') onDragLeaveCallback = new EventEmitter();\n @Output('treeDropDragEnter') onDragEnterCallback = new EventEmitter();\n private readonly dragOverEventHandler: (ev: DragEvent) => void;\n private readonly dragEnterEventHandler: (ev: DragEvent) => void;\n private readonly dragLeaveEventHandler: (ev: DragEvent) => void;\n\n private _allowDrop = (element, $event) => true;\n\n @Input() set treeAllowDrop(allowDrop) {\n if (allowDrop instanceof Function) {\n this._allowDrop = allowDrop;\n } else this._allowDrop = (element, $event) => allowDrop;\n }\n\n allowDrop($event) {\n return this._allowDrop(this.treeDraggedElement.get(), $event);\n }\n\n constructor(\n private el: ElementRef,\n private renderer: Renderer2,\n private treeDraggedElement: TreeDraggedElement,\n private ngZone: NgZone,\n ) {\n this.dragOverEventHandler = this.onDragOver.bind(this);\n this.dragEnterEventHandler = this.onDragEnter.bind(this);\n this.dragLeaveEventHandler = this.onDragLeave.bind(this);\n }\n\n ngAfterViewInit() {\n let el: HTMLElement = this.el.nativeElement;\n this.ngZone.runOutsideAngular(() => {\n el.addEventListener('dragover', this.dragOverEventHandler);\n el.addEventListener('dragenter', this.dragEnterEventHandler);\n el.addEventListener('dragleave', this.dragLeaveEventHandler);\n });\n }\n\n ngOnDestroy() {\n let el: HTMLElement = this.el.nativeElement;\n el.removeEventListener('dragover', this.dragOverEventHandler);\n el.removeEventListener('dragenter', this.dragEnterEventHandler);\n el.removeEventListener('dragleave', this.dragLeaveEventHandler);\n }\n\n onDragOver($event) {\n if (!this.allowDrop($event)) {\n if (this.allowDragoverStyling) {\n return this.addDisabledClass();\n }\n return;\n }\n\n this.onDragOverCallback.emit({\n event: $event,\n element: this.treeDraggedElement.get(),\n });\n\n $event.preventDefault();\n if (this.allowDragoverStyling) {\n this.addClass();\n }\n }\n\n onDragEnter($event) {\n if (!this.allowDrop($event)) return;\n\n $event.preventDefault();\n this.onDragEnterCallback.emit({\n event: $event,\n element: this.treeDraggedElement.get(),\n });\n }\n\n onDragLeave($event) {\n if (!this.allowDrop($event)) {\n if (this.allowDragoverStyling) {\n return this.removeDisabledClass();\n }\n return;\n }\n this.onDragLeaveCallback.emit({\n event: $event,\n element: this.treeDraggedElement.get(),\n });\n\n if (this.allowDragoverStyling) {\n this.removeClass();\n }\n }\n\n @HostListener('drop', ['$event']) onDrop($event) {\n if (!this.allowDrop($event)) return;\n\n $event.preventDefault();\n this.onDropCallback.emit({\n event: $event,\n element: this.treeDraggedElement.get(),\n });\n\n if (this.allowDragoverStyling) {\n this.removeClass();\n }\n this.treeDraggedElement.set(null);\n }\n\n private addClass() {\n this.renderer.addClass(this.el.nativeElement, DRAG_OVER_CLASS);\n }\n\n private removeClass() {\n this.renderer.removeClass(this.el.nativeElement, DRAG_OVER_CLASS);\n }\n\n private addDisabledClass() {\n this.renderer.addClass(this.el.nativeElement, DRAG_DISABLED_CLASS);\n }\n\n private removeDisabledClass() {\n this.renderer.removeClass(this.el.nativeElement, DRAG_DISABLED_CLASS);\n }\n}\n","import { Component, Input, ViewEncapsulation } from '@angular/core';\nimport { TreeNode } from '../models/tree-node.model';\n\n@Component({\n selector: 'TreeNodeDropSlot, tree-node-drop-slot',\n encapsulation: ViewEncapsulation.None,\n styles: [],\n template: `\n <div\n class=\"node-drop-slot\"\n (treeDrop)=\"onDrop($event)\"\n [treeAllowDrop]=\"allowDrop.bind(this)\"\n [allowDragoverStyling]=\"true\"\n ></div>\n `,\n standalone: false,\n})\nexport cla