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