@cisstech/nge
Version:
NG Essentials is a collection of libraries for Angular developers.
956 lines (946 loc) • 43.8 kB
JavaScript
import * as i0 from '@angular/core';
import { Directive, Component, ChangeDetectionStrategy, Input, ContentChild, HostListener, NgModule, Injectable } from '@angular/core';
import { FlatTreeControl } from '@angular/cdk/tree';
import { MatTreeFlattener, MatTreeFlatDataSource } from '@angular/material/tree';
import { BehaviorSubject } from 'rxjs';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i2 from '@angular/forms';
import { FormsModule } from '@angular/forms';
import { ScrollingModule } from '@angular/cdk/scrolling';
/** Element that can be used as a template for a `TreeComponent` */
class TreeNodeDirective {
// https://medium.com/angular-in-depth/type-checking-templates-in-angular-viewengine-and-ivy-77f8536359f5
static ngTemplateContextGuard(_, ctx) {
return true;
}
constructor(templateRef) {
this.templateRef = templateRef;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: TreeNodeDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.1", type: TreeNodeDirective, selector: "[treeNode], ui-tree-node", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: TreeNodeDirective, decorators: [{
type: Directive,
args: [{
// tslint:disable-next-line: directive-selector
selector: '[treeNode], ui-tree-node',
}]
}], ctorParameters: () => [{ type: i0.TemplateRef }] });
/**
* Stores the actual visible trees of the page.
*
* This variable is private to the library and should not be added to the entry file of the api.
*/
const CURRENT_VISIBLE_TREES = new Map();
class TreeFilter {
constructor(term = '') {
this.term = term;
}
}
class TreeState {
constructor(active = '', expandedNodes = [], filter = new TreeFilter()) {
this.active = active;
this.expandedNodes = expandedNodes;
this.filter = filter;
}
}
class AutofocusDirective {
constructor(el) {
this.el = el;
}
ngAfterContentInit() {
setTimeout(() => {
this.el.nativeElement.focus();
}, 500);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: AutofocusDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.1", type: AutofocusDirective, selector: "input[autofocus]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: AutofocusDirective, decorators: [{
type: Directive,
args: [{
// tslint:disable-next-line: directive-selector
selector: 'input[autofocus]',
}]
}], ctorParameters: () => [{ type: i0.ElementRef }] });
class TreeComponent {
get treeHeight() {
return this.adapter?.itemHeight?.toString() || '100%';
}
constructor(elementRef, changeDetectorRef) {
this.elementRef = elementRef;
this.changeDetectorRef = changeDetectorRef;
this.nodes = [];
this.DATA_TREE_NODE_ID = 'data-tree-node-id';
this.nodesIndex = new Map();
this.parentsIndex = new Map();
this.hiddenNodes = new Map();
this.selectedNodes = new Map();
this.isEmpty = false;
this.isShiftKeyPressed = false;
this.editing = { text: '', node: undefined };
this.visibleNodes = new BehaviorSubject([]);
this.filter = new TreeFilter();
this.controler = new FlatTreeControl((node) => node.level, (node) => node.expandable, {
trackBy: (node) => node,
});
this.flattener = new MatTreeFlattener((node, level) => this.transformer(node, level), (node) => node.level, (node) => node.expandable, (node) => this.children(node));
this.dataSource = new MatTreeFlatDataSource(this.controler, this.flattener);
}
ngOnInit() {
if (!this.adapter.id?.trim()) {
throw new Error('@Input() adapter.id is required !');
}
CURRENT_VISIBLE_TREES.set(this.adapter.id, this);
}
ngOnChanges() {
if (!this.adapter) {
throw new Error('@Input() adapter is required !');
}
const requires = ['id', 'idProvider', 'nameProvider', 'isExpandable', 'childrenProvider'];
for (const key of requires) {
const value = this.adapter[key];
if (!value || (typeof value === 'string' && value.trim() === '')) {
throw new Error(` adapter.${key} is required !`);
}
}
this.adapter.itemHeight = this.adapter.itemHeight || 32;
this.adapter.treeHeight = this.adapter.treeHeight || '100%';
this.adapter.keepStateOnChangeNodes = this.adapter.keepStateOnChangeNodes ?? true;
let state;
if (this.adapter.keepStateOnChangeNodes) {
state = this.saveState();
}
this.buildIndexes();
if (state) {
this.restoreState(state);
}
else {
this.unselectAll(false);
this.render();
}
}
ngOnDestroy() {
if (this.adapter?.id) {
CURRENT_VISIBLE_TREES.delete(this.adapter.id);
}
}
//#region API
selections() {
return Array.from(this.selectedNodes.values()).map((e) => e.data);
}
focusedNode() {
return this.activeNode?.data;
}
isFocused(node) {
if (node == null) {
throw new ReferenceError('Argument "node" is required.');
}
if (!this.activeNode) {
return false;
}
return this.findHolder(node)?.id === this.activeNode.id;
}
isExpanded(node) {
if (node == null) {
throw new ReferenceError('Argument "node" is required.');
}
const holder = this.findHolder(node);
if (!holder?.expandable) {
return false;
}
return this.controler.isExpanded(holder);
}
isSelected(node) {
if (node == null) {
throw new ReferenceError('Argument "node" is required.');
}
const holder = this.findHolder(node);
if (!holder) {
return false;
}
return this.selectedNodes.has(holder.id);
}
focus(node, render = true) {
const holder = this.findHolder(node);
if (holder) {
if (this.activeNode) {
this.unselect(this.activeNode, false);
}
this.activeNode = holder;
this.select(node, false);
this.expandAncestors(holder);
if (render) {
this.render();
}
this.scrollInto(holder);
}
}
unfocus() {
this.activeNode = undefined;
this.changeDetectorRef.detectChanges();
}
expand(node, render = true) {
if (!node) {
return;
}
const holder = this.findHolder(node);
if (!holder) {
return;
}
if (holder.expandable && !this.controler.isExpanded(holder)) {
this.controler.expand(holder);
this.expandAncestors(holder);
if (render) {
this.render();
}
}
}
expandAll(render = true) {
this.controler.dataNodes?.forEach((node) => {
this.controler.expand(node);
});
// this.controler.expandAll(); // https://github.com/vmware/clarity/issues/4850
if (render) {
this.render();
}
}
collapse(node, render = true) {
if (!node) {
return;
}
const holder = this.findHolder(node);
if (!holder) {
return;
}
if (holder.expandable && this.controler.isExpanded(holder)) {
this.controler.collapse(holder);
if (this.adapter.onDidCollapse) {
this.adapter.onDidCollapse(holder.data);
}
if (render) {
this.render();
}
}
}
collapseAll(render = true) {
this.controler.collapseAll();
if (render) {
this.render();
}
}
toggle(node, render = true) {
if (!node) {
return;
}
const holder = this.findHolder(node);
if (holder?.expandable) {
if (this.controler.isExpanded(holder)) {
this.collapse(node, render);
}
else {
this.expand(node, render);
}
}
}
startEdition(node, creation) {
if (!this.adapter.onDidEditName)
return;
if (!node) {
throw new ReferenceError('Argument "node" is required.');
}
const holder = this.findHolder(node);
if (holder) {
this.unselectAll(false);
this.editing.node = holder.data;
this.editing.text = creation ? '' : holder.name;
this.editing.creation = creation;
holder.renaming = !creation;
holder.creating = creation;
if (holder.expandable && !this.isExpanded(holder)) {
this.expand(node);
}
}
this.changeDetectorRef.detectChanges();
}
endEdition() {
const holder = this.findHolder(this.editing.node);
if (holder) {
holder.renaming = false;
holder.creating = false;
}
this.editing = { text: '' };
this.changeDetectorRef.detectChanges();
}
search(filter) {
if (!this.filter.term) {
this.stateBeforeSearching = this.saveState();
}
this.filter.term = (filter.term || '').trim();
if (!this.filter.term) {
if (this.stateBeforeSearching) {
this.restoreState(this.stateBeforeSearching);
this.stateBeforeSearching = undefined;
}
else {
this.hiddenNodes.clear();
this.render();
}
return;
}
const pattern = this.buildSearchPattern();
if (pattern) {
this.activeNode = undefined;
this.hiddenNodes.clear();
this.selectedNodes.clear();
this.collapseAll(false);
const p = pattern;
const matches = new Set();
const nodes = this.controler.dataNodes || [];
const n = nodes.length - 1;
for (let i = n; i >= 0; i--) {
const node = nodes[i];
if (matches.has(node.id))
continue;
if (node.name.toLowerCase().match(p)) {
matches.add(node.id);
this.iterateAncestors(node, (e) => {
this.controler.expand(e);
matches.add(e.id);
});
}
else {
this.hiddenNodes.set(node.id, node);
}
}
this.render();
}
else {
this.changeDetectorRef.detectChanges();
}
}
saveState() {
const { term } = this.filter;
const dataNodes = this.controler.dataNodes || [];
const state = {
filter: { term },
active: this.activeNode ? this.activeNode.id : '',
expandedNodes: dataNodes
.filter((e) => {
return this.controler.isExpanded(e);
})
.map((e) => e.id),
};
return state;
}
restoreState(state) {
state = state || {
active: '',
filter: new TreeFilter(),
expandedNodes: [],
};
state.active = state.active || '';
state.filter = state.filter || new TreeFilter();
state.expandedNodes = state.expandedNodes || [];
this.hiddenNodes.clear();
this.selectedNodes.clear();
this.collapseAll(false);
const { active, filter, expandedNodes } = state;
if (filter.term) {
this.search(filter);
}
else {
expandedNodes.forEach((node) => this.expand(node, false));
this.render();
}
if (active) {
const activeNode = this.findHolder(active);
if (activeNode) {
this.focus(activeNode);
}
}
}
//#endregion
//#region CALLED FROM TEMPLATE
_onEdit(event) {
if (this.adapter.onDidEditName) {
event.stopPropagation();
const { node, text, creation } = this.editing;
if (!node) {
throw new Error('There is no focused node.');
}
const isBlur = event instanceof FocusEvent && event.type === 'blur';
const isEnter = event instanceof KeyboardEvent && event.key === 'Enter';
const isEscape = event instanceof KeyboardEvent && event.key === 'Escape';
if (isEscape) {
event.preventDefault();
this.endEdition();
}
else if (isBlur || isEnter) {
event.preventDefault();
const name = text?.trim() || '';
try {
if (name) {
this.adapter.onDidEditName({
node: node,
text: name,
creation,
});
}
}
finally {
this.focus(node);
this.endEdition();
}
}
}
}
_clearFilter() {
this.search({ term: '' });
}
_isRenaming(node) {
if (node == null) {
throw new ReferenceError('Argument "node" is required.');
}
if (!this.editing?.node) {
return false;
}
return !this.editing.creation && this.findHolder(node)?.id === this.adapter.idProvider(this.editing.node);
}
_isCreating(node) {
if (node == null) {
throw new ReferenceError('Argument "node" is required.');
}
if (!this.editing?.node) {
return false;
}
return !!this.editing.creation && this.findHolder(node)?.id === this.adapter.idProvider(this.editing.node);
}
_trackById(_, e) {
return e.id;
}
//#endregion
//#region EVENTS
keyup() {
this.isShiftKeyPressed = false;
}
keydown($event) {
this.isShiftKeyPressed = $event.shiftKey;
if (this.isTreeContainsEvent($event)) {
this.onKeyDown($event);
}
}
mousedown($event) {
this.changeDetectorRef.detach();
if (this.isTreeContainsEvent($event)) {
this.changeDetectorRef.reattach();
this.onMouseDown($event);
}
}
contextmenu($event) {
this.changeDetectorRef.detach();
if (this.isTreeContainsEvent($event)) {
this.changeDetectorRef.reattach();
this.onContextMenu($event);
}
}
onKeyDown(event) {
if (!this.activeNode && !this.isEmpty && !this.selectedNodes.size) {
this.focus(this.controler.dataNodes[0]);
}
const element = this.activeNode ? this.domNode(this.activeNode) : undefined;
if (element && this.activeNode) {
switch (event.key) {
case 'ArrowUp':
event.preventDefault();
event.stopPropagation();
this.navigate(element, 'up');
break;
case 'ArrowDown':
event.preventDefault();
event.stopPropagation();
this.navigate(element, 'down');
break;
case 'ArrowLeft':
if (!this.isShiftKeyPressed) {
event.preventDefault();
event.stopPropagation();
this.collapse(this.activeNode, true);
}
break;
case 'ArrowRight':
if (!this.isShiftKeyPressed) {
event.preventDefault();
event.stopPropagation();
this.expand(this.activeNode, true);
}
break;
default:
this.triggerKeyboardEvent(event);
this.triggerFilterEvent(event);
break;
}
}
else {
this.triggerFilterEvent(event);
}
if (event.key === 'Backspace') {
// prevent browser to navigate back if Backspace is pressed
event.preventDefault();
}
}
onMouseDown(event) {
const node = this.findHolderFromEvent(event);
if (!node) {
return;
}
if (this.isShiftKeyPressed && this.activeNode) {
const domStart = this.domNode(this.activeNode);
if (!domStart) {
this.select(node);
return;
}
const domEnd = this.domNode(node);
if (!domEnd) {
return;
}
// select all nodes between the focused and the target node.
this.unselectAll(false);
let cursor = domStart;
const y2 = domEnd.getClientRects().item(0).top;
const y1 = domStart.getClientRects().item(0).top;
if (y1 < y2) {
// traverse down
do {
this.select(cursor, false);
cursor = cursor.nextElementSibling;
} while (cursor && !cursor.isEqualNode(domEnd));
}
else if (y1 > y2) {
// traverse up
do {
this.select(cursor, false);
cursor = cursor.previousElementSibling;
} while (cursor && !cursor.isEqualNode(domEnd));
}
this.select(domEnd, false);
this.focus(node, true);
}
else {
this.unselectAll(false);
this.toggle(node, false);
this.focus(node, true);
const { actions } = this.adapter;
if (actions?.mouse?.click) {
actions.mouse.click({
node: node.data,
event: event,
});
}
}
}
onContextMenu(e) {
e.preventDefault();
e.stopPropagation();
const node = this.findHolderFromEvent(e);
if (node) {
if (!this.isSelected(node)) {
this.unselectAll(false);
}
this.focus(node, true);
}
const { actions } = this.adapter;
if (actions?.mouse?.rightClick) {
actions.mouse.rightClick({
node: node?.data,
event: e,
});
}
}
isTreeContainsEvent(event) {
return this.elementRef.nativeElement.contains(event.target);
}
triggerFilterEvent(e) {
if (e.defaultPrevented || !this.adapter.enableKeyboardFiltering) {
return;
}
// prevent browser to navigate back if Backspace is pressed
e.preventDefault();
e.stopPropagation();
const { term } = this.filter;
switch (e.key) {
case 'Backspace':
if (term) {
this.search({ term: term.slice(0, -1) });
}
break;
case ' ':
case 'Tab':
break;
default:
if (e.key.length === 1) {
this.search({ term: term + e.key });
}
break;
}
}
triggerKeyboardEvent(event) {
if (!this.activeNode) {
return;
}
const { actions } = this.adapter;
if (actions) {
const { keys } = actions;
if (keys) {
const action = keys[event.key];
if (action) {
action({
event: event,
node: this.activeNode.data,
});
}
}
}
}
//#endregion
//#region PRIVATE
render() {
const nodes = [];
const dataNodes = this.controler.dataNodes || [];
const expandedNodes = new Set(this.controler.expansionModel.selected.map((node) => node.id));
this.isEmpty = true;
dataNodes.forEach((node) => {
this.isEmpty = false;
if (this.hiddenNodes.has(node.id)) {
return;
}
node.focused = this.isFocused(node);
node.expanded = this.isExpanded(node);
node.renaming = this._isRenaming(node);
node.creating = this._isCreating(node);
node.selected = this.isSelected(node);
// root nodes are always visible
if (node.level === 0) {
nodes.push(node);
return;
}
let parent = this.findParent(node);
while (parent != null) {
// hide a node if any of its ancestors is not expanded
if (!expandedNodes.has(parent.id)) {
return;
}
if (parent.level === 0) {
break;
}
parent = this.findParent(parent);
}
nodes.push(node);
});
this.visibleNodes.next(nodes);
this.changeDetectorRef.detectChanges();
}
buildIndexes() {
this.nodesIndex.clear();
this.parentsIndex.clear();
this.dataSource.data = this.nodes;
this.controler.dataNodes?.forEach((node) => {
this.nodesIndex.set(node.id, node);
});
}
buildSearchPattern() {
let pattern;
try {
if (this.filter.term) {
const escape = (text) => {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
};
pattern = new RegExp(`(${escape(this.filter.term)})`, 'g');
}
}
catch (error) {
console.error(error);
}
return pattern;
}
/**
* Gets the dom element associated to the given node.
* @param e A node.
*/
domNode(e) {
const node = this.findHolder(e);
if (!node) {
throw new Error(e + ' is not a registered node');
}
return document.querySelector(`[${this.DATA_TREE_NODE_ID}="${node.id}"]`);
}
/**
* Adds the given node to the selected nodes.
* @param node The node to select.
*/
select(node, detectChanges = true) {
const holder = this.findHolder(node);
if (!holder) {
return;
}
this.selectedNodes.set(holder.id, holder);
if (detectChanges) {
this.changeDetectorRef.detectChanges();
}
}
/**
* Removes the given node to the selected nodes.
* @param node The node to unselect.
*/
unselect(node, detectChanges = true) {
const holder = this.findHolder(node);
if (!holder) {
return;
}
this.selectedNodes.delete(holder.id);
if (holder.id === this.activeNode?.id) {
this.activeNode = undefined;
}
if (detectChanges) {
this.changeDetectorRef.detectChanges();
}
}
/**
* Clears the selected nodes.
*/
unselectAll(detectChanges = true) {
this.activeNode = undefined;
this.selectedNodes.clear();
if (detectChanges) {
this.changeDetectorRef.detectChanges();
}
}
/**
* Expands the ancestors of the given node.
* @param node A node reference.
*/
expandAncestors(node) {
this.iterateAncestors(node, (e) => {
if (e.expandable && !this.controler.isExpanded(e)) {
this.controler.expand(e);
if (this.adapter.onDidExpand) {
this.adapter.onDidExpand(e.data);
}
}
});
}
/**
* Call the given `action` for the ancestors of the given node.
* @param node A node reference.
* @param action A function to call for each ancestor.
*/
iterateAncestors(node, action) {
const recursive = (e) => {
action(e);
const p = this.findParent(e);
if (p && p.level >= 0) {
recursive(p);
}
};
const parent = this.findParent(node);
if (parent) {
recursive(parent);
}
}
/**
* Moves the scrollbar to the given node.
* @param node The node to show.
*/
scrollInto(node) {
const holder = this.findHolder(node);
if (holder) {
const index = this.visibleNodes.value.indexOf(holder);
if (index == -1) {
return;
}
this.domNode(node)?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
/**
* Focus the node before of after `currEl` depending on the given `direction`.
* @param currEl Current element
* @param direction Navigation direction.
*/
navigate(currEl, direction) {
this.unselectAll();
const currNode = this.findHolderFromElement(currEl);
if (!currNode) {
return;
}
const prevEl = currEl.previousElementSibling;
const nextEl = currEl.nextElementSibling;
const targEl = direction === 'up' ? prevEl : nextEl;
if (!targEl) {
return;
}
const targNode = this.findHolderFromElement(targEl);
if (!targNode) {
this.focus(currNode);
return;
}
const prevNode = prevEl ? this.findHolderFromElement(prevEl) : undefined;
if (prevNode && this.isSelected(prevNode) && targEl.isEqualNode(prevEl)) {
this.unselect(currNode);
}
const nextNode = nextEl ? this.findHolderFromElement(nextEl) : undefined;
if (nextNode && this.isSelected(nextNode) && targEl.isEqualNode(nextEl)) {
this.unselect(currNode);
}
this.focus(targNode);
}
findParent(node) {
const holder = this.findHolder(node);
if (!holder) {
return;
}
const parentId = this.parentsIndex.get(holder.id);
if (parentId) {
return this.nodesIndex.get(parentId);
}
return undefined;
}
findHolderFromId(id) {
const { dataNodes } = this.controler;
if (!dataNodes) {
return undefined;
}
return dataNodes.find((n) => n.id === id);
}
findHolderFromData(data) {
const { dataNodes } = this.controler;
if (!dataNodes) {
return undefined;
}
return this.nodesIndex.get(this.adapter.idProvider(data));
}
findHolderFromEvent(event) {
const { dataNodes } = this.controler;
if (!dataNodes) {
return undefined;
}
const dataId = this.DATA_TREE_NODE_ID;
const fn = (target) => {
if (!target) {
return undefined;
}
const targetId = target.getAttribute(dataId);
return targetId ? this.nodesIndex.get(targetId) : fn(target.parentElement);
};
return fn(event.target);
}
findHolderFromElement(element) {
const { dataNodes } = this.controler;
if (!dataNodes) {
return undefined;
}
const id = element.getAttribute(this.DATA_TREE_NODE_ID);
if (!id) {
return undefined;
}
return this.nodesIndex.get(id);
}
findHolder(node) {
if (!node) {
return undefined;
}
if (!this.controler.dataNodes) {
return undefined;
}
if (typeof node === 'string') {
return this.findHolderFromId(node);
}
if (node instanceof Element) {
return this.findHolderFromElement(node);
}
// instanceof ITreeNodeHolder<T>
if ('data' in node) {
return node;
}
// instance of T
return this.findHolderFromData(node);
}
children(node) {
const children = this.adapter.childrenProvider(node) || [];
const parentId = this.adapter.idProvider(node);
children.forEach((child) => {
this.parentsIndex.set(this.adapter.idProvider(child), parentId);
});
return children;
}
transformer(node, level) {
const expandable = this.adapter.isExpandable(node);
return {
id: this.adapter.idProvider(node),
data: node,
name: this.adapter.nameProvider(node),
level: level,
padding: level * 12 + 'px',
tooltip: this.adapter.tooltipProvider?.(node),
expandable,
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: TreeComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.1", type: TreeComponent, selector: "ui-tree", inputs: { nodes: "nodes", adapter: "adapter" }, host: { listeners: { "document:keyup": "keyup()", "document:keydown": "keydown($event)", "document:click": "mousedown($event)", "document:contextmenu": "contextmenu($event)" } }, queries: [{ propertyName: "nodeDirective", first: true, predicate: TreeNodeDirective, descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container *ngIf=\"filter.term\">\n <div class=\"tree-filter\" title=\"Filter\">\n <span class=\"tree-filter__label\">{{ filter.term }}</span>\n <div class=\"tree-filter__commands\">\n <span class=\"command-clear\" role=\"button\" title=\"Clear\" (click)=\"_clearFilter()\">×</span>\n </div>\n </div>\n</ng-container>\n\n<div\n tabindex=\"0\"\n class=\"tree-component\"\n [class.editing]=\"editing.node != null\"\n [style.height]=\"treeHeight\">\n <ng-container *ngFor=\"let node of visibleNodes|async; trackBy: _trackById;\">\n <!-- RENAMING -->\n <ng-container *ngIf=\"node.renaming; else: nodeTemplate\">\n <div\n class=\"tree-node opaque focused selected tree-node--level-{{ node.level + 1 }}\"\n [attr.data-tree-node-id]=\"node.id\"\n [style.padding-left]=\"node.padding\"\n [style.height.px]=\"adapter.itemHeight\">\n <div class=\"tree-node__content\" [style.height.px]=\"adapter.itemHeight\">\n <ng-container *ngTemplateOutlet=\"inputTemplate\" />\n </div>\n </div>\n </ng-container>\n\n <!-- NODE -->\n <ng-template #nodeTemplate>\n <div\n class=\"tree-node tree-node--level-{{ node.level }}\"\n [attr.data-tree-node-id]=\"node.id\"\n [style.padding-left]=\"node.padding\"\n [style.height.px]=\"adapter.itemHeight\"\n [ngClass]=\"{\n focused: node.focused,\n selected: node.selected,\n expanded: node.expanded\n }\">\n <div class=\"tree-node__content\" [style.height.px]=\"adapter.itemHeight\">\n <ng-container *ngTemplateOutlet=\"nodeDirective.templateRef; context: { $implicit: node }\" />\n </div>\n </div>\n </ng-template>\n\n <!-- CREATING -->\n <ng-container *ngIf=\"node.creating\">\n <div\n class=\"tree-node opaque focused selected tree-node--level-{{ node.level + 1 }}\"\n [style.padding-left]=\"node.padding\"\n [style.height.px]=\"adapter.itemHeight\">\n <div class=\"tree-node__content\" [style.height.px]=\"adapter.itemHeight\">\n <ng-container *ngTemplateOutlet=\"inputTemplate\" />\n </div>\n </div>\n </ng-container>\n </ng-container>\n</div>\n\n<ng-template #inputTemplate>\n <span class=\"tree-input\">\n <input\n autofocus\n type=\"text\"\n placeholder=\"Press Enter to create ESC to cancel...\"\n (click)=\"$event.preventDefault(); $event.stopPropagation()\"\n [(ngModel)]=\"editing.text\"\n (keydown)=\"_onEdit($event)\"\n (blur)=\"_onEdit($event)\" />\n </span>\n</ng-template>\n", styles: [":host{position:relative;--tree-selection-color: #bae7ff}.tree-component{height:100%;background:transparent}.tree-component:hover{outline:none}.tree-component:hover .tree-node.focused,.tree-component:hover .tree-node.selected{background-color:var(--tree-selection-color)}.tree-component.editing .opaque{opacity:1!important}.tree-component.editing .tree-node{opacity:.4}.tree-node:hover,.tree-node.focused,.tree-node.selected{background-color:#4242420a}.tree-node,.tree-node__content{cursor:pointer;position:relative;width:100%;display:flex;overflow:hidden;align-items:center;box-sizing:border-box}.tree-node__content{padding:0 12px}.tree-input{display:flex;align-items:center;width:calc(100% - 4px);margin-right:4px}.tree-input input{width:100%;margin:0 0 0 8px;padding:.1rem .3rem;font-size:.8rem;line-height:1.5;background-clip:padding-box;border:1px solid #ced4da;border-radius:.15rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tree-input input:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem #007bff40}.tree-filter{background-color:#efc1ad;display:flex;align-items:center;position:absolute;border-radius:2px;padding:0 4px;max-width:calc(100% - 10px);text-overflow:ellipsis;overflow:hidden;text-align:right;box-sizing:border-box;cursor:all-scroll;font-size:13px;line-height:18px;height:20px;z-index:1;right:0;outline:rgba(0,0,0,0) solid 4px;transition:width .5s}.tree-filter:hover .tree-filter__commands{display:flex}.tree-filter .tree-filter__commands{display:none;display:flex;align-items:center}.tree-filter .tree-filter__commands span{cursor:pointer;font-size:18px;margin-left:4px}.tree-filter .command-filter{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: AutofocusDirective, selector: "input[autofocus]" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: TreeComponent, decorators: [{
type: Component,
args: [{ selector: 'ui-tree', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *ngIf=\"filter.term\">\n <div class=\"tree-filter\" title=\"Filter\">\n <span class=\"tree-filter__label\">{{ filter.term }}</span>\n <div class=\"tree-filter__commands\">\n <span class=\"command-clear\" role=\"button\" title=\"Clear\" (click)=\"_clearFilter()\">×</span>\n </div>\n </div>\n</ng-container>\n\n<div\n tabindex=\"0\"\n class=\"tree-component\"\n [class.editing]=\"editing.node != null\"\n [style.height]=\"treeHeight\">\n <ng-container *ngFor=\"let node of visibleNodes|async; trackBy: _trackById;\">\n <!-- RENAMING -->\n <ng-container *ngIf=\"node.renaming; else: nodeTemplate\">\n <div\n class=\"tree-node opaque focused selected tree-node--level-{{ node.level + 1 }}\"\n [attr.data-tree-node-id]=\"node.id\"\n [style.padding-left]=\"node.padding\"\n [style.height.px]=\"adapter.itemHeight\">\n <div class=\"tree-node__content\" [style.height.px]=\"adapter.itemHeight\">\n <ng-container *ngTemplateOutlet=\"inputTemplate\" />\n </div>\n </div>\n </ng-container>\n\n <!-- NODE -->\n <ng-template #nodeTemplate>\n <div\n class=\"tree-node tree-node--level-{{ node.level }}\"\n [attr.data-tree-node-id]=\"node.id\"\n [style.padding-left]=\"node.padding\"\n [style.height.px]=\"adapter.itemHeight\"\n [ngClass]=\"{\n focused: node.focused,\n selected: node.selected,\n expanded: node.expanded\n }\">\n <div class=\"tree-node__content\" [style.height.px]=\"adapter.itemHeight\">\n <ng-container *ngTemplateOutlet=\"nodeDirective.templateRef; context: { $implicit: node }\" />\n </div>\n </div>\n </ng-template>\n\n <!-- CREATING -->\n <ng-container *ngIf=\"node.creating\">\n <div\n class=\"tree-node opaque focused selected tree-node--level-{{ node.level + 1 }}\"\n [style.padding-left]=\"node.padding\"\n [style.height.px]=\"adapter.itemHeight\">\n <div class=\"tree-node__content\" [style.height.px]=\"adapter.itemHeight\">\n <ng-container *ngTemplateOutlet=\"inputTemplate\" />\n </div>\n </div>\n </ng-container>\n </ng-container>\n</div>\n\n<ng-template #inputTemplate>\n <span class=\"tree-input\">\n <input\n autofocus\n type=\"text\"\n placeholder=\"Press Enter to create ESC to cancel...\"\n (click)=\"$event.preventDefault(); $event.stopPropagation()\"\n [(ngModel)]=\"editing.text\"\n (keydown)=\"_onEdit($event)\"\n (blur)=\"_onEdit($event)\" />\n </span>\n</ng-template>\n", styles: [":host{position:relative;--tree-selection-color: #bae7ff}.tree-component{height:100%;background:transparent}.tree-component:hover{outline:none}.tree-component:hover .tree-node.focused,.tree-component:hover .tree-node.selected{background-color:var(--tree-selection-color)}.tree-component.editing .opaque{opacity:1!important}.tree-component.editing .tree-node{opacity:.4}.tree-node:hover,.tree-node.focused,.tree-node.selected{background-color:#4242420a}.tree-node,.tree-node__content{cursor:pointer;position:relative;width:100%;display:flex;overflow:hidden;align-items:center;box-sizing:border-box}.tree-node__content{padding:0 12px}.tree-input{display:flex;align-items:center;width:calc(100% - 4px);margin-right:4px}.tree-input input{width:100%;margin:0 0 0 8px;padding:.1rem .3rem;font-size:.8rem;line-height:1.5;background-clip:padding-box;border:1px solid #ced4da;border-radius:.15rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tree-input input:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem #007bff40}.tree-filter{background-color:#efc1ad;display:flex;align-items:center;position:absolute;border-radius:2px;padding:0 4px;max-width:calc(100% - 10px);text-overflow:ellipsis;overflow:hidden;text-align:right;box-sizing:border-box;cursor:all-scroll;font-size:13px;line-height:18px;height:20px;z-index:1;right:0;outline:rgba(0,0,0,0) solid 4px;transition:width .5s}.tree-filter:hover .tree-filter__commands{display:flex}.tree-filter .tree-filter__commands{display:none;display:flex;align-items:center}.tree-filter .tree-filter__commands span{cursor:pointer;font-size:18px;margin-left:4px}.tree-filter .command-filter{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { nodes: [{
type: Input
}], adapter: [{
type: Input
}], nodeDirective: [{
type: ContentChild,
args: [TreeNodeDirective, { static: true }]
}], keyup: [{
type: HostListener,
args: ['document:keyup']
}], keydown: [{
type: HostListener,
args: ['document:keydown', ['$event']]
}], mousedown: [{
type: HostListener,
args: ['document:click', ['$event']]
}], contextmenu: [{
type: HostListener,
args: ['document:contextmenu', ['$event']]
}] } });
class NgeUiTreeModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeUiTreeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.1", ngImport: i0, type: NgeUiTreeModule, declarations: [TreeComponent, TreeNodeDirective, AutofocusDirective], imports: [CommonModule, FormsModule, ScrollingModule], exports: [TreeComponent, TreeNodeDirective] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeUiTreeModule, imports: [CommonModule, FormsModule, ScrollingModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeUiTreeModule, decorators: [{
type: NgModule,
args: [{
declarations: [TreeComponent, TreeNodeDirective, AutofocusDirective],
imports: [CommonModule, FormsModule, ScrollingModule],
exports: [TreeComponent, TreeNodeDirective],
}]
}] });
class TreeService {
/**
* Gets the tree identified by `id` if the tree is visible.
* @param id Identifier of a tree.
*/
get(id) {
return CURRENT_VISIBLE_TREES.get(id);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: TreeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: TreeService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: TreeService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { NgeUiTreeModule, TreeComponent, TreeFilter, TreeNodeDirective, TreeService, TreeState };
//# sourceMappingURL=cisstech-nge-ui-tree.mjs.map