ng2-tree
Version:
angular2 component for visualizing data that can be naturally represented as a tree
1 lines • 135 kB
Source Map (JSON)
{"version":3,"file":"ng2-tree.mjs","sources":["../../../src/tree.events.ts","../../../src/draggable/draggable.events.ts","../../../src/draggable/node-draggable.service.ts","../../../src/utils/fn.utils.ts","../../../src/tree.service.ts","../../../src/rxjs-imports.ts","../../../src/tree.types.ts","../../../src/tree.ts","../../../src/menu/menu.events.ts","../../../src/utils/event.utils.ts","../../../src/tree-controller.ts","../../../src/editable/editable.events.ts","../../../src/menu/node-menu.service.ts","../../../src/draggable/captured-node.ts","../../../src/draggable/node-draggable.directive.ts","../../../src/editable/node-editable.directive.ts","../../../src/menu/node-menu.component.ts","../../../src/utils/safe-html.pipe.ts","../../../src/tree-internal.component.ts","../../../src/tree.component.ts","../../../src/tree.module.ts","../../../src/ng2-tree.ts"],"sourcesContent":["import { Tree } from './tree';\nimport { RenamableNode } from './tree.types';\n\nexport class NodeEvent {\n public constructor(public node: Tree) {}\n}\n\nexport class NodeSelectedEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class NodeUnselectedEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class NodeDestructiveEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class NodeMovedEvent extends NodeDestructiveEvent {\n public constructor(node: Tree, public previousParent: Tree) {\n super(node);\n }\n}\n\nexport class NodeRemovedEvent extends NodeDestructiveEvent {\n public constructor(node: Tree, public lastIndex: number) {\n super(node);\n }\n}\n\nexport class NodeCreatedEvent extends NodeDestructiveEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class NodeRenamedEvent extends NodeDestructiveEvent {\n public constructor(node: Tree, public oldValue: string | RenamableNode, public newValue: string | RenamableNode) {\n super(node);\n }\n}\n\nexport class NodeExpandedEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class NodeCollapsedEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class MenuItemSelectedEvent extends NodeEvent {\n public constructor(node: Tree, public selectedItem: string) {\n super(node);\n }\n}\n\nexport class LoadNextLevelEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class NodeCheckedEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class NodeUncheckedEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n\nexport class NodeIndeterminedEvent extends NodeEvent {\n public constructor(node: Tree) {\n super(node);\n }\n}\n","import { ElementRef } from '@angular/core';\nimport { CapturedNode } from './captured-node';\n\nexport class NodeDraggableEvent {\n public constructor(public captured: CapturedNode, public target: ElementRef) {}\n}\n","import { ElementRef, Injectable } from '@angular/core';\nimport { CapturedNode } from './captured-node';\nimport { NodeDraggableEvent } from './draggable.events';\nimport { Subject } from 'rxjs';\n\n@Injectable()\nexport class NodeDraggableService {\n public draggableNodeEvents$: Subject<NodeDraggableEvent> = new Subject<NodeDraggableEvent>();\n\n private capturedNode: CapturedNode;\n\n public fireNodeDragged(captured: CapturedNode, target: ElementRef): void {\n if (!captured.tree || captured.tree.isStatic()) {\n return;\n }\n\n this.draggableNodeEvents$.next(new NodeDraggableEvent(captured, target));\n }\n\n public captureNode(node: CapturedNode): void {\n this.capturedNode = node;\n }\n\n public getCapturedNode(): CapturedNode {\n return this.capturedNode;\n }\n\n public releaseCapturedNode(): void {\n this.capturedNode = null;\n }\n}\n","export function isEmpty(value: any[] | string): boolean {\n if (typeof value === 'string') {\n return !/\\S/.test(value);\n }\n\n if (Array.isArray(value)) {\n return value.length === 0;\n }\n\n return isNil(value);\n}\n\nexport function trim(value: string): string {\n return isNil(value) ? '' : value.trim();\n}\n\nexport function has(value: any, prop: string): boolean {\n return value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, prop);\n}\n\nexport function isFunction(value: any) {\n return typeof value === 'function';\n}\n\nexport function get(value: any, path: string, defaultValue?: any) {\n let result = value;\n\n for (const prop of path.split('.')) {\n if (!result || !Reflect.has(result, prop)) {\n return defaultValue;\n }\n\n result = result[prop];\n }\n\n return isNil(result) || result === value ? defaultValue : result;\n}\n\nexport function omit(value: any, propsToSkip: string | string[]): any {\n if (!value) {\n return value;\n }\n\n const normalizedPropsToSkip = typeof propsToSkip === 'string' ? [propsToSkip] : propsToSkip;\n\n return Object.keys(value).reduce((result, prop) => {\n if (includes(normalizedPropsToSkip, prop)) {\n return result;\n }\n return Object.assign(result, { [prop]: value[prop] });\n }, {});\n}\n\nexport function size(value: any[]): number {\n return isEmpty(value) ? 0 : value.length;\n}\n\nexport function once(fn: Once): Once {\n let result;\n\n return (...args: any[]) => {\n if (fn) {\n result = fn(...args);\n fn = null;\n }\n return result;\n };\n}\n\nexport function defaultsDeep(target: any, ...sources: any[]): any {\n return [target].concat(sources).reduce((result: any, source: any) => {\n if (!source) {\n return result;\n }\n\n Object.keys(source).forEach(prop => {\n if (isNil(result[prop])) {\n result[prop] = source[prop];\n return;\n }\n\n if (typeof result[prop] === 'object' && !Array.isArray(result[prop])) {\n result[prop] = defaultsDeep(result[prop], source[prop]);\n return;\n }\n });\n\n return result;\n }, {});\n}\n\nexport function includes(target: string | any[], value: any): boolean {\n if (isNil(target)) {\n return false;\n }\n\n const index = typeof target === 'string' ? target.indexOf(value as string) : target.indexOf(value);\n return index > -1;\n}\n\nexport function isNil(value: any): boolean {\n return value === undefined || value === null;\n}\n\nexport type Once = (...args: any[]) => any;\n","import {\n LoadNextLevelEvent,\n MenuItemSelectedEvent,\n NodeCheckedEvent,\n NodeCollapsedEvent,\n NodeCreatedEvent,\n NodeExpandedEvent,\n NodeIndeterminedEvent,\n NodeMovedEvent,\n NodeRemovedEvent,\n NodeRenamedEvent,\n NodeSelectedEvent,\n NodeUncheckedEvent,\n NodeUnselectedEvent\n} from './tree.events';\nimport { RenamableNode } from './tree.types';\nimport { Tree } from './tree';\nimport { TreeController } from './tree-controller';\nimport { ElementRef, Inject, Injectable } from '@angular/core';\nimport { NodeDraggableService } from './draggable/node-draggable.service';\nimport { NodeDraggableEvent } from './draggable/draggable.events';\nimport { isEmpty } from './utils/fn.utils';\nimport { Observable, Subject } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\n@Injectable()\nexport class TreeService {\n public nodeMoved$: Subject<NodeMovedEvent> = new Subject<NodeMovedEvent>();\n public nodeRemoved$: Subject<NodeRemovedEvent> = new Subject<NodeRemovedEvent>();\n public nodeRenamed$: Subject<NodeRenamedEvent> = new Subject<NodeRenamedEvent>();\n public nodeCreated$: Subject<NodeCreatedEvent> = new Subject<NodeCreatedEvent>();\n public nodeSelected$: Subject<NodeSelectedEvent> = new Subject<NodeSelectedEvent>();\n public nodeUnselected$: Subject<NodeUnselectedEvent> = new Subject<NodeUnselectedEvent>();\n public nodeExpanded$: Subject<NodeExpandedEvent> = new Subject<NodeExpandedEvent>();\n public nodeCollapsed$: Subject<NodeCollapsedEvent> = new Subject<NodeCollapsedEvent>();\n public menuItemSelected$: Subject<MenuItemSelectedEvent> = new Subject<MenuItemSelectedEvent>();\n public loadNextLevel$: Subject<LoadNextLevelEvent> = new Subject<LoadNextLevelEvent>();\n public nodeChecked$: Subject<NodeCheckedEvent> = new Subject<NodeCheckedEvent>();\n public nodeUnchecked$: Subject<NodeUncheckedEvent> = new Subject<NodeUncheckedEvent>();\n public nodeIndetermined$: Subject<NodeIndeterminedEvent> = new Subject<NodeIndeterminedEvent>();\n\n private controllers: Map<string | number, TreeController> = new Map();\n\n public constructor(@Inject(NodeDraggableService) private nodeDraggableService: NodeDraggableService) {\n this.nodeRemoved$.subscribe((e: NodeRemovedEvent) => e.node.removeItselfFromParent());\n }\n\n public unselectStream(tree: Tree): Observable<NodeSelectedEvent> {\n return this.nodeSelected$.pipe(filter((e: NodeSelectedEvent) => tree !== e.node));\n }\n\n public fireNodeRemoved(tree: Tree): void {\n this.nodeRemoved$.next(new NodeRemovedEvent(tree, tree.positionInParent));\n }\n\n public fireNodeCreated(tree: Tree): void {\n this.nodeCreated$.next(new NodeCreatedEvent(tree));\n }\n\n public fireNodeSelected(tree: Tree): void {\n this.nodeSelected$.next(new NodeSelectedEvent(tree));\n }\n\n public fireNodeUnselected(tree: Tree): void {\n this.nodeUnselected$.next(new NodeUnselectedEvent(tree));\n }\n\n public fireNodeRenamed(oldValue: RenamableNode | string, tree: Tree): void {\n this.nodeRenamed$.next(new NodeRenamedEvent(tree, oldValue, tree.value));\n }\n\n public fireNodeMoved(tree: Tree, parent: Tree): void {\n this.nodeMoved$.next(new NodeMovedEvent(tree, parent));\n }\n\n public fireMenuItemSelected(tree: Tree, selectedItem: string): void {\n this.menuItemSelected$.next(new MenuItemSelectedEvent(tree, selectedItem));\n }\n\n public fireNodeSwitchFoldingType(tree: Tree): void {\n if (tree.isNodeExpanded()) {\n this.fireNodeExpanded(tree);\n if (this.shouldFireLoadNextLevel(tree)) {\n this.fireLoadNextLevel(tree);\n }\n } else if (tree.isNodeCollapsed()) {\n this.fireNodeCollapsed(tree);\n }\n }\n\n private fireNodeExpanded(tree: Tree): void {\n this.nodeExpanded$.next(new NodeExpandedEvent(tree));\n }\n\n private fireNodeCollapsed(tree: Tree): void {\n this.nodeCollapsed$.next(new NodeCollapsedEvent(tree));\n }\n\n private fireLoadNextLevel(tree: Tree): void {\n this.loadNextLevel$.next(new LoadNextLevelEvent(tree));\n }\n\n public fireNodeChecked(tree: Tree): void {\n this.nodeChecked$.next(new NodeCheckedEvent(tree));\n }\n\n public fireNodeUnchecked(tree: Tree): void {\n this.nodeUnchecked$.next(new NodeUncheckedEvent(tree));\n }\n\n public draggedStream(tree: Tree, element: ElementRef): Observable<NodeDraggableEvent> {\n return this.nodeDraggableService.draggableNodeEvents$.pipe(\n filter((e: NodeDraggableEvent) => e.target === element),\n filter((e: NodeDraggableEvent) => !e.captured.tree.hasChild(tree))\n );\n }\n\n public setController(id: string | number, controller: TreeController): void {\n this.controllers.set(id, controller);\n }\n\n public deleteController(id: string | number): void {\n if (this.controllers.has(id)) {\n this.controllers.delete(id);\n }\n }\n\n public getController(id: string | number): TreeController {\n if (this.controllers.has(id)) {\n return this.controllers.get(id);\n }\n\n return null;\n }\n\n public hasController(id: string | number): boolean {\n return this.controllers.has(id);\n }\n\n private shouldFireLoadNextLevel(tree: Tree): boolean {\n const shouldLoadNextLevel =\n tree.node.emitLoadNextLevel &&\n !tree.node.loadChildren &&\n !tree.childrenAreBeingLoaded() &&\n isEmpty(tree.children);\n\n if (shouldLoadNextLevel) {\n tree.loadingChildrenRequested();\n }\n\n return shouldLoadNextLevel;\n }\n\n public fireNodeIndetermined(tree: Tree): void {\n this.nodeIndetermined$.next(new NodeIndeterminedEvent(tree));\n }\n}\n","import { filter, merge } from 'rxjs/operators';\nimport { of } from 'rxjs';\n\n// This forces angular compiler to generate a \"rxjs-imports.metadata.json\"\n// with a valid metadata instead of \"[null]\"\nexport const noop = () => {};\n","import { defaultsDeep, get, omit } from './utils/fn.utils';\nimport { NodeMenuItem } from './menu/node-menu.component';\n\nexport class FoldingType {\n public static Expanded: FoldingType = new FoldingType('node-expanded');\n public static Collapsed: FoldingType = new FoldingType('node-collapsed');\n public static Empty: FoldingType = new FoldingType('node-empty');\n public static Leaf: FoldingType = new FoldingType('node-leaf');\n\n public constructor(private _cssClass: string) {}\n\n public get cssClass(): string {\n return this._cssClass;\n }\n}\n\nexport type ChildrenLoadingFunction = (callback: (children: TreeModel[]) => void) => void;\n\nexport interface TreeModel {\n value: string | RenamableNode;\n id?: string | number;\n children?: TreeModel[];\n loadChildren?: ChildrenLoadingFunction;\n settings?: TreeModelSettings;\n emitLoadNextLevel?: boolean;\n _status?: TreeStatus;\n _foldingType?: FoldingType;\n [additionalData: string]: any;\n}\n\nexport interface CssClasses {\n /* The class or classes that should be added to the expanded node */\n expanded?: string;\n\n /* The class or classes that should be added to the collapsed node */\n collapsed?: string;\n\n /* The class or classes that should be added to the empty node */\n empty?: string;\n\n /* The class or classes that should be added to the expanded to the leaf */\n leaf?: string;\n}\n\nexport interface Templates {\n /* A template for a node */\n node?: string;\n\n /* A template for a leaf node */\n leaf?: string;\n\n /* A template for left menu html element */\n leftMenu?: string;\n}\n\nexport class TreeModelSettings {\n /* cssClasses - set custom css classes which will be used for a tree */\n public cssClasses?: CssClasses;\n\n /* Templates - set custom html templates to be used in a tree */\n public templates?: Templates;\n\n /**\n * \"leftMenu\" property when set to true makes left menu available.\n * @name TreeModelSettings#leftMenu\n * @type boolean\n * @default false\n */\n public leftMenu?: boolean;\n\n /**\n * \"rightMenu\" property when set to true makes right menu available.\n * @name TreeModelSettings#rightMenu\n * @type boolean\n * @default true\n */\n public rightMenu?: boolean;\n\n /**\n * \"menu\" property when set will be available as custom context menu.\n * @name TreeModelSettings#MenuItems\n * @type NodeMenuItem\n */\n public menuItems?: NodeMenuItem[];\n\n /**\n * \"static\" property when set to true makes it impossible to drag'n'drop tree or call a menu on it.\n * @name TreeModelSettings#static\n * @type boolean\n * @default false\n */\n public static?: boolean;\n\n public isCollapsedOnInit?: boolean;\n\n public checked?: boolean;\n\n public selectionAllowed?: boolean;\n\n public keepNodesInDOM?: boolean;\n\n public static readonly NOT_CASCADING_SETTINGS = ['selectionAllowed'];\n\n public static merge(child: TreeModel, parent: TreeModel): TreeModelSettings {\n const parentCascadingSettings = omit(get(parent, 'settings'), TreeModelSettings.NOT_CASCADING_SETTINGS);\n return defaultsDeep({}, get(child, 'settings'), parentCascadingSettings, {\n static: false,\n leftMenu: false,\n rightMenu: true,\n isCollapsedOnInit: false,\n checked: false,\n keepNodesInDOM: false,\n selectionAllowed: true\n });\n }\n}\n\nexport class Ng2TreeSettings {\n /**\n * Indicates root visibility in the tree. When true - root is invisible.\n * @name Ng2TreeSettings#rootIsVisible\n * @type boolean\n */\n rootIsVisible? = true;\n showCheckboxes? = false;\n enableCheckboxes? = true;\n}\n\nexport enum TreeStatus {\n New,\n Modified,\n IsBeingRenamed\n}\n\nexport interface RenamableNode {\n /**\n * Set new value of the renamable node. Implementation of this method is up to user.\n * @param {string} name - A new value of the node.\n */\n setName(name: string): void;\n\n /**\n * Get string representation of the node. Implementation of this method is up to user.\n * @returns {string} - A node string representation.\n */\n toString(): string;\n}\n","import { defaultsDeep, get, has, includes, isEmpty, isFunction, isNil, omit, once, size, trim } from './utils/fn.utils';\n\nimport {\n ChildrenLoadingFunction,\n FoldingType,\n RenamableNode,\n TreeModel,\n TreeModelSettings,\n TreeStatus\n} from './tree.types';\nimport { NodeMenuItem } from './menu/node-menu.component';\n\nimport * as uuidv4 from 'uuid/v4';\nimport { Observable, Observer, of } from 'rxjs';\n\nenum ChildrenLoadingState {\n NotStarted,\n Loading,\n Completed\n}\n\nexport class Tree {\n private _children: Tree[];\n private _loadChildren: ChildrenLoadingFunction;\n private _childrenLoadingState: ChildrenLoadingState = ChildrenLoadingState.NotStarted;\n private _childrenAsyncOnce: () => Observable<Tree[]> = once(() => {\n return new Observable((observer: Observer<Tree[]>) => {\n setTimeout(() => {\n this._childrenLoadingState = ChildrenLoadingState.Loading;\n this._loadChildren((children: TreeModel[]) => {\n this._children = (children || []).map((child: TreeModel) => new Tree(child, this));\n this._childrenLoadingState = ChildrenLoadingState.Completed;\n observer.next(this.children);\n observer.complete();\n });\n });\n });\n });\n\n public node: TreeModel;\n public parent: Tree;\n\n // STATIC METHODS ----------------------------------------------------------------------------------------------------\n\n /**\n * Check that value passed is not empty (it doesn't consist of only whitespace symbols).\n * @param {string} value - A value that should be checked.\n * @returns {boolean} - A flag indicating that value is empty or not.\n * @static\n */\n public static isValueEmpty(value: string): boolean {\n return isEmpty(trim(value));\n }\n\n /**\n * Check whether a given value can be considered RenamableNode.\n * @param {any} value - A value to check.\n * @returns {boolean} - A flag indicating whether given value is Renamable node or not.\n * @static\n */\n public static isRenamable(value: any): value is RenamableNode {\n return (\n has(value, 'setName') &&\n isFunction(value.setName) &&\n (has(value, 'toString') && isFunction(value.toString) && value.toString !== Object.toString)\n );\n }\n\n private static cloneTreeShallow(origin: Tree): Tree {\n const tree = new Tree(Object.assign({}, origin.node));\n tree._children = origin._children;\n return tree;\n }\n\n private static applyNewValueToRenamable(value: RenamableNode, newValue: string): RenamableNode {\n const renamableValue: RenamableNode = Object.assign({}, value as RenamableNode);\n renamableValue.setName(newValue);\n return renamableValue;\n }\n\n /**\n * Build an instance of Tree from an object implementing TreeModel interface.\n * @param {TreeModel} model - A model that is used to build a tree.\n * @param {Tree} [parent] - An optional parent if you want to build a tree from the model that should be a child of an existing Tree instance.\n * @param {boolean} [isBranch] - An option that makes a branch from created tree. Branch can have children.\n */\n public constructor(node: TreeModel, parent: Tree = null, isBranch: boolean = false) {\n this.buildTreeFromModel(node, parent, isBranch || Array.isArray(node.children));\n }\n\n private buildTreeFromModel(model: TreeModel, parent: Tree, isBranch: boolean): void {\n this.parent = parent;\n this.node = Object.assign(\n omit(model, 'children') as TreeModel,\n { settings: TreeModelSettings.merge(model, get(parent, 'node')) },\n { emitLoadNextLevel: model.emitLoadNextLevel === true }\n ) as TreeModel;\n\n if (isFunction(this.node.loadChildren)) {\n this._loadChildren = this.node.loadChildren;\n } else {\n get(model, 'children', []).forEach((child: TreeModel, index: number) => {\n this._addChild(new Tree(child, this), index);\n });\n }\n\n if (!Array.isArray(this._children)) {\n this._children = this.node.loadChildren || isBranch ? [] : null;\n }\n }\n\n public hasDeferredChildren(): boolean {\n return typeof this._loadChildren === 'function';\n }\n /* Setting the children loading state to Loading since a request was dispatched to the client */\n public loadingChildrenRequested(): void {\n this._childrenLoadingState = ChildrenLoadingState.Loading;\n }\n\n /**\n * Check whether children of the node are being loaded.\n * Makes sense only for nodes that define `loadChildren` function.\n * @returns {boolean} A flag indicating that children are being loaded.\n */\n public childrenAreBeingLoaded(): boolean {\n return this._childrenLoadingState === ChildrenLoadingState.Loading;\n }\n\n /**\n * Check whether children of the node were loaded.\n * Makes sense only for nodes that define `loadChildren` function.\n * @returns {boolean} A flag indicating that children were loaded.\n */\n public childrenWereLoaded(): boolean {\n return this._childrenLoadingState === ChildrenLoadingState.Completed;\n }\n\n private canLoadChildren(): boolean {\n return (\n this._childrenLoadingState === ChildrenLoadingState.NotStarted &&\n this.foldingType === FoldingType.Expanded &&\n !!this._loadChildren\n );\n }\n\n /**\n * Check whether children of the node should be loaded and not loaded yet.\n * Makes sense only for nodes that define `loadChildren` function.\n * @returns {boolean} A flag indicating that children should be loaded for the current node.\n */\n public childrenShouldBeLoaded(): boolean {\n return !this.childrenWereLoaded() && (!!this._loadChildren || this.node.emitLoadNextLevel === true);\n }\n\n /**\n * Get children of the current tree.\n * @returns {Tree[]} The children of the current tree.\n */\n public get children(): Tree[] {\n return this._children;\n }\n\n /**\n * By getting value from this property you start process of loading node's children using `loadChildren` function.\n * Once children are loaded `loadChildren` function won't be called anymore and loaded for the first time children are emitted in case of subsequent calls.\n * @returns {Observable<Tree[]>} An observable which emits children once they are loaded.\n */\n public get childrenAsync(): Observable<Tree[]> {\n if (this.canLoadChildren()) {\n return this._childrenAsyncOnce();\n }\n return of(this.children);\n }\n\n /**\n * By calling this method you start process of loading node's children using `loadChildren` function.\n */\n public reloadChildren(): void {\n if (this.childrenShouldBeLoaded()) {\n this._childrenLoadingState = ChildrenLoadingState.Loading;\n this._loadChildren((children: TreeModel[]) => {\n this._children = children && children.map((child: TreeModel) => new Tree(child, this));\n this._childrenLoadingState = ChildrenLoadingState.Completed;\n });\n }\n }\n\n /**\n * By calling this method you will remove all current children of a treee and create new.\n */\n public setChildren(children: Array<TreeModel>): void {\n this._children = children && children.map((child: TreeModel) => new Tree(child, this));\n if (this.childrenShouldBeLoaded()) {\n this._childrenLoadingState = ChildrenLoadingState.Completed;\n }\n }\n\n /**\n * Create a new node in the current tree.\n * @param {boolean} isBranch - A flag that indicates whether a new node should be a \"Branch\". \"Leaf\" node will be created by default\n * @param {TreeModel} model - Tree model of the new node which will be inserted. Empty node will be created by default and it will fire edit mode of this node\n * @returns {Tree} A newly created child node.\n */\n public createNode(isBranch: boolean, model: TreeModel = { value: '' }): Tree {\n const tree = new Tree(model, this, isBranch);\n if (!model.id) {\n tree.markAsNew();\n }\n\n tree.id = tree.id || uuidv4();\n\n if (this.childrenShouldBeLoaded() && !(this.childrenAreBeingLoaded() || this.childrenWereLoaded())) {\n return null;\n }\n if (this.isLeaf()) {\n return this.addSibling(tree);\n } else {\n return this.addChild(tree);\n }\n }\n\n /**\n * Get the value of the current node\n * @returns {(string|RenamableNode)} The value of the node.\n */\n public get value(): any {\n return this.node.value;\n }\n\n public set checked(checked: boolean) {\n this.node.settings = Object.assign({}, this.node.settings, { checked });\n }\n\n public get checked(): boolean {\n return !!get(this.node.settings, 'checked');\n }\n\n public get checkedChildren(): Tree[] {\n return this.hasLoadedChildern() ? this.children.filter(child => child.checked) : [];\n }\n\n public set selectionAllowed(selectionAllowed: boolean) {\n this.node.settings = Object.assign({}, this.node.settings, { selectionAllowed });\n }\n\n public get selectionAllowed(): boolean {\n const value = get(this.node.settings, 'selectionAllowed');\n return isNil(value) ? true : !!value;\n }\n\n hasLoadedChildern() {\n return !isEmpty(this.children);\n }\n\n loadedChildrenAmount() {\n return size(this.children);\n }\n\n checkedChildrenAmount() {\n return size(this.checkedChildren);\n }\n\n /**\n * Set the value of the current node\n * @param {(string|RenamableNode)} value - The new value of the node.\n */\n public set value(value: any) {\n if (typeof value !== 'string' && !Tree.isRenamable(value)) {\n return;\n }\n\n const stringifiedValue = '' + value;\n if (Tree.isRenamable(this.value)) {\n this.node.value = Tree.applyNewValueToRenamable(this.value as RenamableNode, stringifiedValue);\n } else {\n this.node.value = Tree.isValueEmpty(stringifiedValue) ? this.node.value : stringifiedValue;\n }\n }\n\n /**\n * Add a sibling node for the current node. This won't work if the current node is a root.\n * @param {Tree} sibling - A node that should become a sibling.\n * @param [number] position - Position in which sibling will be inserted. By default it will be inserted at the last position in a parent.\n * @returns {Tree} A newly inserted sibling, or null if you are trying to make a sibling for the root.\n */\n public addSibling(sibling: Tree, position?: number): Tree {\n if (Array.isArray(get(this.parent, 'children'))) {\n return this.parent.addChild(sibling, position);\n }\n return null;\n }\n\n /**\n * Add a child node for the current node.\n * @param {Tree} child - A node that should become a child.\n * @param [number] position - Position in which child will be inserted. By default it will be inserted at the last position in a parent.\n * @returns {Tree} A newly inserted child.\n */\n public addChild(child: Tree, position?: number): Tree {\n const newborn = this._addChild(Tree.cloneTreeShallow(child), position);\n\n this._setFoldingType();\n if (this.isNodeCollapsed()) {\n this.switchFoldingType();\n }\n\n return newborn;\n }\n\n private _addChild(child: Tree, position: number = size(this._children) || 0): Tree {\n child.parent = this;\n\n if (Array.isArray(this._children)) {\n this._children.splice(position, 0, child);\n } else {\n this._children = [child];\n }\n\n return child;\n }\n\n /**\n * Swap position of the current node with the given sibling. If node passed as a parameter is not a sibling - nothing happens.\n * @param {Tree} sibling - A sibling with which current node shold be swapped.\n */\n public swapWithSibling(sibling: Tree): void {\n if (!this.hasSibling(sibling)) {\n return;\n }\n\n const siblingIndex = sibling.positionInParent;\n const thisTreeIndex = this.positionInParent;\n\n this.parent._children[siblingIndex] = this;\n this.parent._children[thisTreeIndex] = sibling;\n }\n\n /**\n * Get a node's position in its parent.\n * @returns {number} The position inside a parent.\n */\n public get positionInParent(): number {\n if (this.isRoot()) {\n return -1;\n }\n\n return this.parent.children ? this.parent.children.indexOf(this) : -1;\n }\n\n /**\n * Check whether or not this tree is static.\n * @returns {boolean} A flag indicating whether or not this tree is static.\n */\n public isStatic(): boolean {\n return get(this.node.settings, 'static', false);\n }\n\n /**\n * Check whether or not this tree has a left menu.\n * @returns {boolean} A flag indicating whether or not this tree has a left menu.\n */\n public hasLeftMenu(): boolean {\n return !get(this.node.settings, 'static', false) && get(this.node.settings, 'leftMenu', false);\n }\n\n /**\n * Check whether or not this tree has a right menu.\n * @returns {boolean} A flag indicating whether or not this tree has a right menu.\n */\n public hasRightMenu(): boolean {\n return !get(this.node.settings, 'static', false) && get(this.node.settings, 'rightMenu', false);\n }\n\n /**\n * Check whether this tree is \"Leaf\" or not.\n * @returns {boolean} A flag indicating whether or not this tree is a \"Leaf\".\n */\n public isLeaf(): boolean {\n return !this.isBranch();\n }\n\n /**\n * Get menu items of the current tree.\n * @returns {NodeMenuItem[]} The menu items of the current tree.\n */\n public get menuItems(): NodeMenuItem[] {\n return get(this.node.settings, 'menuItems');\n }\n\n /**\n * Check whether or not this tree has a custom menu.\n * @returns {boolean} A flag indicating whether or not this tree has a custom menu.\n */\n public hasCustomMenu(): boolean {\n return !this.isStatic() && !!get(this.node.settings, 'menuItems', false);\n }\n /**\n * Check whether this tree is \"Branch\" or not. \"Branch\" is a node that has children.\n * @returns {boolean} A flag indicating whether or not this tree is a \"Branch\".\n */\n public isBranch(): boolean {\n return this.node.emitLoadNextLevel === true || Array.isArray(this._children);\n }\n\n /**\n * Check whether this tree has children.\n * @returns {boolean} A flag indicating whether or not this tree has children.\n */\n public hasChildren(): boolean {\n return !isEmpty(this._children) || this.childrenShouldBeLoaded();\n }\n\n /**\n * Check whether this tree is a root or not. The root is the tree (node) that doesn't have parent (or technically its parent is null).\n * @returns {boolean} A flag indicating whether or not this tree is the root.\n */\n public isRoot(): boolean {\n return isNil(this.parent);\n }\n\n /**\n * Check whether provided tree is a sibling of the current tree. Sibling trees (nodes) are the trees that have the same parent.\n * @param {Tree} tree - A tree that should be tested on a siblingness.\n * @returns {boolean} A flag indicating whether or not provided tree is the sibling of the current one.\n */\n public hasSibling(tree: Tree): boolean {\n return !this.isRoot() && includes(this.parent.children, tree);\n }\n\n /**\n * Check whether provided tree is a child of the current tree.\n * This method tests that provided tree is a <strong>direct</strong> child of the current tree.\n * @param {Tree} tree - A tree that should be tested (child candidate).\n * @returns {boolean} A flag indicating whether provided tree is a child or not.\n */\n public hasChild(tree: Tree): boolean {\n return includes(this._children, tree);\n }\n\n /**\n * Remove given tree from the current tree.\n * The given tree will be removed only in case it is a direct child of the current tree (@see {@link hasChild}).\n * @param {Tree} tree - A tree that should be removed.\n */\n public removeChild(tree: Tree): void {\n if (!this.hasChildren()) {\n return;\n }\n\n const childIndex = this._children.findIndex((child: Tree) => child === tree);\n if (childIndex >= 0) {\n this._children.splice(childIndex, 1);\n }\n this._setFoldingType();\n }\n\n /**\n * Remove current tree from its parent.\n */\n public removeItselfFromParent(): void {\n if (!this.parent) {\n return;\n }\n\n this.parent.removeChild(this);\n }\n\n /**\n * Switch folding type of the current tree. \"Leaf\" node cannot switch its folding type cause it doesn't have children, hence nothing to fold.\n * If node is a \"Branch\" and it is expanded, then by invoking current method state of the tree should be switched to \"collapsed\" and vice versa.\n */\n public switchFoldingType(): void {\n if (this.isLeaf() || !this.hasChildren()) {\n return;\n }\n\n this.disableCollapseOnInit();\n\n this.node._foldingType = this.isNodeExpanded() ? FoldingType.Collapsed : FoldingType.Expanded;\n }\n\n /**\n * Check that tree is expanded.\n * @returns {boolean} A flag indicating whether current tree is expanded. Always returns false for the \"Leaf\" tree and for an empty tree.\n */\n public isNodeExpanded(): boolean {\n return this.foldingType === FoldingType.Expanded;\n }\n\n /**\n * Check that tree is collapsed.\n * @returns {boolean} A flag indicating whether current tree is collapsed. Always returns false for the \"Leaf\" tree and for an empty tree.\n */\n public isNodeCollapsed(): boolean {\n return this.foldingType === FoldingType.Collapsed;\n }\n\n /**\n * Set a current folding type: expanded, collapsed or leaf.\n */\n private _setFoldingType(): void {\n if (this.childrenShouldBeLoaded()) {\n this.node._foldingType = FoldingType.Collapsed;\n } else if (this._children && !isEmpty(this._children)) {\n this.node._foldingType = this.isCollapsedOnInit() ? FoldingType.Collapsed : FoldingType.Expanded;\n } else if (Array.isArray(this._children)) {\n this.node._foldingType = FoldingType.Empty;\n } else {\n this.node._foldingType = FoldingType.Leaf;\n }\n }\n\n /**\n * Get a current folding type: expanded, collapsed or leaf.\n * @returns {FoldingType} A folding type of the current tree.\n */\n public get foldingType(): FoldingType {\n if (!this.node._foldingType) {\n this._setFoldingType();\n }\n return this.node._foldingType;\n }\n\n /**\n * Get a css class for element which displayes folding state - expanded, collapsed or leaf\n * @returns {string} A string icontaining css class (classes)\n */\n public get foldingCssClass(): string {\n return this.getCssClassesFromSettings() || this.foldingType.cssClass;\n }\n\n private getCssClassesFromSettings(): string {\n if (!this.node._foldingType) {\n this._setFoldingType();\n }\n\n if (this.node._foldingType === FoldingType.Collapsed) {\n return get(this.node.settings, 'cssClasses.collapsed', null);\n } else if (this.node._foldingType === FoldingType.Expanded) {\n return get(this.node.settings, 'cssClasses.expanded', null);\n } else if (this.node._foldingType === FoldingType.Empty) {\n return get(this.node.settings, 'cssClasses.empty', null);\n }\n\n return get(this.node.settings, 'cssClasses.leaf', null);\n }\n\n /**\n * Get a html template to render before every node's name.\n * @returns {string} A string representing a html template.\n */\n public get nodeTemplate(): string {\n return this.getTemplateFromSettings();\n }\n\n private getTemplateFromSettings(): string {\n if (this.isLeaf()) {\n return get(this.node.settings, 'templates.leaf', '');\n } else {\n return get(this.node.settings, 'templates.node', '');\n }\n }\n\n /**\n * Get a html template to render for an element activatin left menu of a node.\n * @returns {string} A string representing a html template.\n */\n public get leftMenuTemplate(): string {\n if (this.hasLeftMenu()) {\n return get(this.node.settings, 'templates.leftMenu', '<span></span>');\n }\n return '';\n }\n\n public disableCollapseOnInit() {\n if (this.node.settings) {\n this.node.settings.isCollapsedOnInit = false;\n }\n }\n\n public isCollapsedOnInit() {\n return !!get(this.node.settings, 'isCollapsedOnInit');\n }\n\n public keepNodesInDOM() {\n return get(this.node.settings, 'keepNodesInDOM');\n }\n\n /**\n * Check that current tree is newly created (added by user via menu for example). Tree that was built from the TreeModel is not marked as new.\n * @returns {boolean} A flag whether the tree is new.\n */\n public isNew(): boolean {\n return this.node._status === TreeStatus.New;\n }\n\n public get id(): number | string {\n return get(this.node, 'id');\n }\n\n public set id(id: number | string) {\n this.node.id = id;\n }\n\n /**\n * Mark current tree as new (@see {@link isNew}).\n */\n public markAsNew(): void {\n this.node._status = TreeStatus.New;\n }\n\n /**\n * Check that current tree is being renamed (it is in the process of its value renaming initiated by a user).\n * @returns {boolean} A flag whether the tree is being renamed.\n */\n public isBeingRenamed(): boolean {\n return this.node._status === TreeStatus.IsBeingRenamed;\n }\n\n /**\n * Mark current tree as being renamed (@see {@link isBeingRenamed}).\n */\n public markAsBeingRenamed(): void {\n this.node._status = TreeStatus.IsBeingRenamed;\n }\n\n /**\n * Check that current tree is modified (for example it was renamed).\n * @returns {boolean} A flag whether the tree is modified.\n */\n public isModified(): boolean {\n return this.node._status === TreeStatus.Modified;\n }\n\n /**\n * Mark current tree as modified (@see {@link isModified}).\n */\n public markAsModified(): void {\n this.node._status = TreeStatus.Modified;\n }\n\n /**\n * Makes a clone of an underlying TreeModel instance\n * @returns {TreeModel} a clone of an underlying TreeModel instance\n */\n public toTreeModel(): TreeModel {\n const model = defaultsDeep(this.isLeaf() ? {} : { children: [] }, this.node);\n\n if (this.children) {\n this.children.forEach(child => {\n model.children.push(child.toTreeModel());\n });\n }\n\n return model;\n }\n}\n","export enum NodeMenuItemAction {\n NewFolder,\n NewTag,\n Rename,\n Remove,\n Custom\n}\n\nexport enum NodeMenuAction {\n Close\n}\n\nexport interface NodeMenuEvent {\n sender: HTMLElement;\n action: NodeMenuAction;\n}\n\nexport interface NodeMenuItemSelectedEvent {\n nodeMenuItemAction: NodeMenuItemAction;\n nodeMenuItemSelected?: string;\n}\n","export enum Keys {\n Escape = 27\n}\n\nexport enum MouseButtons {\n Left = 0,\n Right = 2\n}\n\nexport function isLeftButtonClicked(e: MouseEvent): boolean {\n return e.button === MouseButtons.Left;\n}\n\nexport function isRightButtonClicked(e: MouseEvent): boolean {\n return e.button === MouseButtons.Right;\n}\n\nexport function isEscapePressed(e: KeyboardEvent): boolean {\n return e.keyCode === Keys.Escape;\n}\n","import { TreeService } from './tree.service';\nimport { Tree } from './tree';\nimport { TreeModel } from './tree.types';\nimport { NodeMenuItemAction } from './menu/menu.events';\nimport { TreeInternalComponent } from './tree-internal.component';\nimport { MouseButtons } from './utils/event.utils';\nimport { get } from './utils/fn.utils';\n\nexport class TreeController {\n private tree: Tree;\n private treeService: TreeService;\n\n constructor(private component: TreeInternalComponent) {\n this.tree = this.component.tree;\n this.treeService = this.component.treeService;\n }\n\n public select(): void {\n if (!this.isSelected()) {\n this.component.onNodeSelected({ button: MouseButtons.Left });\n }\n }\n\n public unselect(): void {\n if (this.isSelected()) {\n this.component.onNodeUnselected({ button: MouseButtons.Left });\n }\n }\n\n public isSelected(): boolean {\n return this.component.isSelected;\n }\n\n public expand(): void {\n if (this.isCollapsed()) {\n this.component.onSwitchFoldingType();\n }\n }\n\n public expandToParent(tree: any = this.tree): void {\n if (tree) {\n const controller = this.treeService.getController(tree.id);\n if (controller) {\n requestAnimationFrame(() => {\n controller.expand();\n this.expandToParent(tree.parent);\n });\n }\n }\n }\n\n public isExpanded(): boolean {\n return this.tree.isNodeExpanded();\n }\n\n public collapse(): void {\n if (this.isExpanded()) {\n this.component.onSwitchFoldingType();\n }\n }\n\n public isCollapsed(): boolean {\n return this.tree.isNodeCollapsed();\n }\n\n public toTreeModel(): TreeModel {\n return this.tree.toTreeModel();\n }\n\n public rename(newValue: string): void {\n this.tree.markAsBeingRenamed();\n this.component.applyNewValue({ type: 'keyup', value: newValue });\n }\n\n public remove(): void {\n this.component.onMenuItemSelected({ nodeMenuItemAction: NodeMenuItemAction.Remove });\n }\n\n public addChild(newNode: TreeModel): void {\n if (this.tree.hasDeferredChildren() && !this.tree.childrenWereLoaded()) {\n return;\n }\n\n const newTree = this.tree.createNode(Array.isArray(newNode.children), newNode);\n this.treeService.fireNodeCreated(newTree);\n }\n\n public addChildAsync(newNode: TreeModel): Promise<Tree> {\n if (this.tree.hasDeferredChildren() && !this.tree.childrenWereLoaded()) {\n return Promise.reject(\n new Error('This node loads its children asynchronously, hence child cannot be added this way')\n );\n }\n\n const newTree = this.tree.createNode(Array.isArray(newNode.children), newNode);\n this.treeService.fireNodeCreated(newTree);\n\n // This will give TreeInternalComponent to set up a controller for the node\n return new Promise(resolve => {\n setTimeout(() => {\n resolve(newTree);\n });\n });\n }\n\n public changeNodeId(id: string | number) {\n if (!id) {\n throw Error('You should supply an id!');\n }\n\n if (this.treeService.hasController(id)) {\n throw Error(`Controller already exists for the given id: ${id}`);\n }\n\n this.treeService.deleteController(this.tree.id);\n this.tree.id = id;\n this.treeService.setController(this.tree.id, this);\n }\n\n public reloadChildren(): void {\n this.tree.reloadChildren();\n }\n\n public setChildren(children: TreeModel[]): void {\n if (!this.tree.isLeaf()) {\n this.tree.setChildren(children);\n }\n }\n\n public startRenaming(): void {\n this.tree.markAsBeingRenamed();\n }\n\n public check(): void {\n this.component.onNodeChecked();\n }\n\n public uncheck(): void {\n this.component.onNodeUnchecked();\n }\n\n public isChecked(): boolean {\n return this.tree.checked;\n }\n\n public isIndetermined(): boolean {\n return get(this.component, 'checkboxElementRef.nativeElement.indeterminate');\n }\n\n public allowSelection() {\n this.tree.selectionAllowed = true;\n }\n\n public forbidSelection() {\n this.tree.selectionAllowed = false;\n }\n\n public isSelectionAllowed(): boolean {\n return this.tree.selectionAllowed;\n }\n}\n","export type NodeEditableEventType = 'blur' | 'keyup';\n\nexport enum NodeEditableEventAction {\n Cancel\n}\n\nexport interface NodeEditableEvent {\n value: string;\n type: NodeEditableEventType;\n action?: NodeEditableEventAction;\n}\n","import { ElementRef, Injectable } from '@angular/core';\nimport { NodeMenuAction, NodeMenuEvent } from './menu.events';\nimport { Observable, Subject } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\n@Injectable()\nexport class NodeMenuService {\n public nodeMenuEvents$: Subject<NodeMenuEvent> = new Subject<NodeMenuEvent>();\n\n public fireMenuEvent(sender: HTMLElement, action: NodeMenuAction): void {\n const nodeMenuEvent: NodeMenuEvent = { sender, action };\n this.nodeMenuEvents$.next(nodeMenuEvent);\n }\n\n public hideMenuStream(treeElementRef: ElementRef): Observable<any> {\n return this.nodeMenuEvents$.pipe(\n filter((e: NodeMenuEvent) => treeElementRef.nativeElement !== e.sender),\n filter((e: NodeMenuEvent) => e.action === NodeMenuAction.Close)\n );\n }\n\n public hideMenuForAllNodesExcept(treeElementRef: ElementRef): void {\n this.nodeMenuEvents$.next({\n sender: treeElementRef.nativeElement,\n action: NodeMenuAction.Close\n });\n }\n}\n","import { Tree } from '../tree';\nimport { ElementRef } from '@angular/core';\n\nexport class CapturedNode {\n public constructor(private anElement: ElementRef, private aTree: Tree) {}\n\n public canBeDroppedAt(element: ElementRef): boolean {\n return !this.sameAs(element) && !this.contains(element);\n }\n\n public contains(other: ElementRef): boolean {\n return this.element.nativeElement.contains(other.nativeElement);\n }\n\n public sameAs(other: ElementRef): boolean {\n return this.element === other;\n }\n\n public get element(): ElementRef {\n return this.anElement;\n }\n\n public get tree(): Tree {\n return this.aTree;\n }\n}\n","import { Directive, ElementRef, Inject, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core';\nimport { NodeDraggableService } from './node-draggable.service';\nimport { CapturedNode } from './captured-node';\nimport { Tree } from '../tree';\n\n@Directive({\n selector: '[nodeDraggable]'\n})\nexport class NodeDraggableDirective implements OnDestroy, OnInit {\n public static DATA_TRANSFER_STUB_DATA = 'some browsers enable drag-n-drop only when dataTransfer has data';\n\n @Input() public nodeDraggable: ElementRef;\n\n @Input() public tree: Tree;\n\n private nodeNativeElement: HTMLElement;\n private disposersForDragListeners: (() => void)[] = [];\n\n public constructor(\n @Inject(ElementRef) public element: ElementRef,\n @Inject(NodeDraggableService) private nodeDraggableService: NodeDraggableService,\n @Inject(Renderer2) private renderer: Renderer2\n ) {\n this.nodeNativeElement = element.nativeElement;\n }\n\n public ngOnInit(): void {\n if (!this.tree.isStatic()) {\n this.renderer.setAttribute(this.nodeNativeElement, 'draggable', 'true');\n this.disposersForDragListeners.push(\n this.renderer.listen(this.nodeNativeElement, 'dragenter', this.handleDragEnter.bind(this))\n );\n this.disposersForDragListeners.push(\n this.renderer.listen(this.nodeNativeElement, 'dragover', this.handleDragOver.bind(this))\n );\n this.disposersForDragListeners.push(\n this.renderer.listen(this.nodeNativeElement, 'dragstart', this.handleDragStart.bind(this))\n );\n this.disposersForDragListeners.push(\n this.renderer.listen(this.nodeNativeElement, 'dragleave', this.handleDragLeave.bind(this))\n );\n this.disposersForDragListeners.push(\n this.renderer.listen(this.nodeNativeElement, 'drop', this.handleDrop.bind(this))\n );\n this.disposersForDragListeners.push(\n this.renderer.listen(this.nodeNativeElement, 'dragend', this.handleDragEnd.bind(this))\n );\n }\n }\n\n public ngOnDestroy(): void {\n /* tslint:disable:typedef */\n this.disposersForDragListeners.forEach(dispose => dispose());\n /* tslint:enable:typedef */\n }\n\n private handleDragStart(e: DragEvent): any {\n if (e.stopPropagation) {\n e.stopPropagation();\n }\n\n this.nodeDraggableService.captureNode(new CapturedNode(this.nodeDraggable, this.tree));\n\n e.dataTransfer.setData('text', NodeDraggableDirective.DATA_TRANSFER_STUB_DATA);\n e.dataTransfer.effectAllowed = 'move';\n }\n\n private handleDragOver(e: DragEvent): any {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }\n\n private handleDragEnter(e: DragEvent): any {\n e.preventDefault();\n if (this.containsElementAt(e)) {\n this.addClass('over-drop-target');\n }\n }\n\n private handleDragLeave(e: DragEvent): any {\n if (!this.containsElementAt(e)) {\n this.removeClass('over-drop-target');\n }\n }\n\n private handleDrop(e: DragEvent): any {\n e.preventDefault();\n if (e.stopPropagation) {\n e.stopPropagation();\n }\n\n this.removeClass('over-drop-target');\n\n if (!this.isDropPossible(e)) {\n return false;\n }\n\n if (this.nodeDraggableService.getCapturedNode()) {\n return this.notifyThatNodeWasDropped();\n }\n }\n\n private isDropPossible(e: DragEvent): boolean {\n const capturedNode = this.nodeDraggableService.getCapturedNode();\n return capturedNode && capturedNode.canBeDroppedAt(this.nodeDraggable) && this.containsElementAt(e);\n }\n\n private handleDragEnd(e: DragEvent): any {\n this.removeClass('over-drop-target');\n this.nodeDraggableService.releaseCapturedNode();\n }\n\n private containsElementAt(e: DragEvent): boolean {\n const { x = e.clientX, y = e.clientY } = e;\n return this.nodeNativeElement.contains(document.elementFromPoint(x, y));\n }\n\n private addClass(className: string): void {\n const classList: DOMTokenList = this.nodeNativeElement.classList;\n classList.add(className);\n }\n\n private removeClass(className: string): void {\n const classList: DOMTokenList = this.nodeNativeElement.classList;\n classList.remove(className);\n }\n\n private notifyThatNodeWasDropped(): void {\n this.nodeDraggableService.fireNodeDragged(this.nodeDraggableService.getCa