@tapsellorg/angular-material-library
Version:
Angular library for Tapsell
364 lines (356 loc) • 25.2 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, input, output, signal, ContentChild, ChangeDetectionStrategy, ViewEncapsulation, Component, NgModule } from '@angular/core';
import * as i4 from '@angular/material/tree';
import { MatTreeFlattener, MatTreeFlatDataSource, MatTreeModule } from '@angular/material/tree';
import { SelectionModel } from '@angular/cdk/collections';
import { FlatTreeControl } from '@angular/cdk/tree';
import { BehaviorSubject } from 'rxjs';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i3 from '@angular/material/checkbox';
import { MatCheckboxModule } from '@angular/material/checkbox';
import * as i5 from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import * as i6 from '@angular/material/form-field';
import { MatFormFieldModule } from '@angular/material/form-field';
import * as i7 from '@angular/material/input';
import { MatInputModule } from '@angular/material/input';
import * as i8 from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
const getLevel = (node) => node.level;
const isExpandable = (node) => node.expandable;
const getChildren = (node) => node.children;
const hasChild = (_, _nodeData) => _nodeData.expandable;
const hasNoContent = (_, _nodeData) => _nodeData.item === '';
class PghTreeService {
get data() {
return this.dataChange.value;
}
constructor() {
this.dataChange = new BehaviorSubject([]);
this.TREE_DATA = {};
this.initialize();
}
initialize() {
const data = this.buildFileTree(this.TREE_DATA, 0);
this.dataChange.next(data);
}
buildFileTree(obj, level) {
return Object.keys(obj).reduce((accumulator, key) => {
const value = obj[key];
const node = { item: key };
node.item = key;
if (value != null) {
if (typeof value === 'object') {
node.children = this.buildFileTree(value, level + 1);
}
else {
node.item = value;
}
}
return accumulator.concat(node);
}, []);
}
insertItem(parent, name) {
parent.children?.push({ item: name });
this.dataChange.next(this.data);
}
updateItem(node, name) {
node.item = name;
this.dataChange.next(this.data);
}
setTreeData(data) {
this.TREE_DATA = data;
this.initialize();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghTreeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghTreeService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghTreeService, decorators: [{
type: Injectable
}], ctorParameters: () => [] });
const TreeUtils = {
treeControl: undefined,
setTreeControl(treeControl) {
this.treeControl = treeControl;
},
removeEstimateCount(str) {
return str.includes(',') ? str.substring(0, str.indexOf(',')) : str;
},
getFullPathWithoutEstimateCount(node) {
const parentItems = [...this.getParentNodes(node, true)]
.reverse()
.map(parent => this.removeEstimateCount(parent.item));
const currentItem = this.removeEstimateCount(node.item);
return [...parentItems, currentItem];
},
cutTreeDataUpToLevel(tree, depth) {
if (depth <= 0) {
return {};
}
const result = {};
for (const [key, value] of Object.entries(tree)) {
if (typeof value === 'object' && value) {
result[key] = this.cutTreeDataUpToLevel(value, depth - 1);
}
else {
result[key] = value;
}
}
return result;
},
createTreeFlattener(flatNodeMap, nestedNodeMap) {
return new MatTreeFlattener((node, level) => {
const existing = nestedNodeMap.get(node);
const flatNode = existing?.item === node.item ? existing : { item: '', level: 0, expandable: false };
flatNode.item = node.item;
flatNode.level = level;
flatNode.expandable = !!node.children?.length;
flatNodeMap.set(flatNode, node);
nestedNodeMap.set(node, flatNode);
return flatNode;
}, node => node.level, node => node.expandable, node => node.children);
},
getParentNodes(node, shouldReturnAllNodes = false) {
const allNodes = this.treeControl?.dataNodes;
if (!allNodes)
return [];
const parentNodes = [];
let currentLevel = getLevel(node);
if (currentLevel < 1) {
return parentNodes;
}
const startIndex = allNodes.indexOf(node) - 1;
for (let i = startIndex; i >= 0; i--) {
const currentNode = allNodes[i];
if (getLevel(currentNode) < currentLevel) {
parentNodes.push(currentNode);
currentLevel--;
if (!shouldReturnAllNodes) {
break;
}
}
}
return parentNodes;
},
getChildrenNodes(node) {
if (!this.treeControl)
return [];
const descendants = this.treeControl.getDescendants(node);
return descendants.filter(child => this.treeControl.getLevel(child) === this.treeControl.getLevel(node) + 1);
},
getSelectedNodes(selectedPaths) {
const allNodes = this.treeControl?.dataNodes;
if (!allNodes?.length || !selectedPaths?.length)
return;
const allNodesWithPath = allNodes.map(node => {
const fullPath = TreeUtils.getFullPathWithoutEstimateCount(node);
return {
node,
path: fullPath,
};
});
return selectedPaths
.map(path => allNodesWithPath.find(n => JSON.stringify(n.path) === JSON.stringify(path)))
.filter(match => !!match)
.map(match => match.node);
},
handleTreeExpansion(shouldExpandTree) {
if (this.treeControl) {
this.treeControl[shouldExpandTree ? 'expandAll' : 'collapseAll']();
}
},
};
class PghTreeComponent {
constructor(pghTreeService) {
this.pghTreeService = pghTreeService;
this.hasChild = hasChild;
this.hasNoContent = hasNoContent;
this.treeUtils = TreeUtils;
this.treeDataSource = input(undefined);
this.treeLabels = input();
this.treeDataSourceDepth = input(100);
this.selectable = input(true);
this.shouldExpandTree = input(false);
this.canAddChildNode = input(true);
this.selectedPaths = input([]);
this.selectedItemsChanged = output();
this.treeControl = signal(undefined);
this.dataSource = signal(undefined);
this.checklistSelection = signal(new SelectionModel(true));
this.flatNodeMap = new Map();
this.nestedNodeMap = new Map();
this.treeFlattener = TreeUtils.createTreeFlattener(this.flatNodeMap, this.nestedNodeMap);
}
ngOnInit() {
this.initializeTree();
this.onChangeTree();
this.patchSelectedNodes();
}
ngAfterViewInit() {
TreeUtils.handleTreeExpansion(this.shouldExpandTree());
}
ngOnChanges(changes) {
if ((changes.treeDataSource || changes.treeDataSourceDepth) && this.treeDataSource()) {
this.pghTreeService.setTreeData(TreeUtils.cutTreeDataUpToLevel(this.treeDataSource(), this.treeDataSourceDepth()));
}
if (changes.shouldExpandTree || changes.treeDataSource) {
TreeUtils.handleTreeExpansion(this.shouldExpandTree());
}
}
initializeTree() {
this.treeControl.set(new FlatTreeControl(getLevel, isExpandable));
this.dataSource.set(new MatTreeFlatDataSource(this.treeControl(), this.treeFlattener));
TreeUtils.setTreeControl(this.treeControl());
}
onChangeTree() {
this.pghTreeService.dataChange.subscribe(data => {
if (!this.dataSource())
return;
this.dataSource().data = data;
});
}
patchSelectedNodes() {
const selectedNodes = TreeUtils.getSelectedNodes(this.selectedPaths());
if (!selectedNodes?.length)
return;
this.checklistSelection().clear();
for (const node of selectedNodes) {
const descendants = this.treeControl().getDescendants(node);
this.checklistSelection().select(node, ...descendants);
}
this.storeSelectedNodes();
}
leafItemSelectionToggle(node) {
this.checklistSelection().toggle(node);
this.checkAllParentsSelection(node);
}
addNewItem(node) {
const parentNode = this.flatNodeMap.get(node);
if (parentNode &&
this.treeControl() &&
getChildren(parentNode)?.filter(n => n.item === '').length === 0) {
this.pghTreeService.insertItem(parentNode, '');
this.treeControl().expand(node);
}
}
saveNode(node, itemValue) {
const nestedNode = this.flatNodeMap.get(node);
if (nestedNode) {
this.pghTreeService.updateItem(nestedNode, itemValue);
}
}
descendantsAllSelected(node) {
if (!this.treeControl())
return;
const descendants = this.treeControl().getDescendants(node);
return (descendants.length > 0 &&
descendants.every(child => this.checklistSelection().isSelected(child)));
}
descendantsPartiallySelected(node) {
if (!this.treeControl())
return;
const descendants = this.treeControl().getDescendants(node);
const result = descendants.some(child => this.checklistSelection().isSelected(child));
return result && !this.descendantsAllSelected(node);
}
parentItemSelectionToggle(node) {
if (!this.treeControl())
return;
this.checklistSelection().toggle(node);
const descendants = this.treeControl().getDescendants(node);
this.checklistSelection().isSelected(node)
? this.checklistSelection().select(...descendants)
: this.checklistSelection().deselect(...descendants);
descendants.forEach(child => this.checklistSelection().isSelected(child));
this.checkAllParentsSelection(node);
}
checkAllParentsSelection(node) {
let parent = TreeUtils.getParentNodes(node, false)[0];
while (parent) {
this.checkRootNodeSelection(parent);
parent = TreeUtils.getParentNodes(parent, false)[0];
}
}
checkRootNodeSelection(node) {
if (!this.treeControl())
return;
const nodeSelected = this.checklistSelection().isSelected(node);
const descAllSelected = this.descendantsAllSelected(node);
if (nodeSelected && !descAllSelected) {
this.checklistSelection().deselect(node);
}
else if (!nodeSelected && descAllSelected) {
this.checklistSelection().select(node);
}
}
flatNodeToNestedNode(selectedOption) {
return {
item: selectedOption.item,
parents: TreeUtils.getParentNodes(selectedOption, true)?.reverse(),
children: TreeUtils.getChildrenNodes(selectedOption),
label: this.treeLabels() ? this.treeLabels()[selectedOption.item] : undefined,
};
}
storeSelectedNodes() {
const selectedFlatNodes = this.checklistSelection().selected;
const selectedNestedNodes = selectedFlatNodes.map(v => this.flatNodeToNestedNode(v));
// remove node if its parent is selected
const selectedNodes = new Set(selectedNestedNodes.map(node => node.item));
const filteredSelectedNodes = selectedNestedNodes.filter(node => {
const parentItems = node.parents?.map(parent => parent.item) ?? [];
return !parentItems.some(parent => selectedNodes.has(parent));
});
this.selectedItemsChanged.emit(filteredSelectedNodes);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghTreeComponent, deps: [{ token: PghTreeService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.13", type: PghTreeComponent, isStandalone: false, selector: "pgh-tree", inputs: { treeDataSource: { classPropertyName: "treeDataSource", publicName: "treeDataSource", isSignal: true, isRequired: false, transformFunction: null }, treeLabels: { classPropertyName: "treeLabels", publicName: "treeLabels", isSignal: true, isRequired: false, transformFunction: null }, treeDataSourceDepth: { classPropertyName: "treeDataSourceDepth", publicName: "treeDataSourceDepth", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, shouldExpandTree: { classPropertyName: "shouldExpandTree", publicName: "shouldExpandTree", isSignal: true, isRequired: false, transformFunction: null }, canAddChildNode: { classPropertyName: "canAddChildNode", publicName: "canAddChildNode", isSignal: true, isRequired: false, transformFunction: null }, selectedPaths: { classPropertyName: "selectedPaths", publicName: "selectedPaths", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItemsChanged: "selectedItemsChanged" }, providers: [PghTreeService], queries: [{ propertyName: "customNodeItemTemplate", first: true, predicate: ["customNodeItemTemplate"], descendants: true }, { propertyName: "customElementTemplate", first: true, predicate: ["customElementTemplate"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<mat-tree [dataSource]=\"dataSource()!\" [treeControl]=\"treeControl()!\">\n <mat-tree-node *matTreeNodeDef=\"let node\" matTreeNodeToggle matTreeNodePadding>\n <button mat-icon-button disabled></button>\n <ng-container\n *ngTemplateOutlet=\"nodeTemplate; context: { node: node, isParent: false }\"\n ></ng-container>\n </mat-tree-node>\n\n @if (canAddChildNode()) {\n <mat-tree-node *matTreeNodeDef=\"let node; when: hasNoContent\" matTreeNodePadding>\n <button mat-icon-button disabled></button>\n <mat-form-field appearance=\"fill\">\n <mat-label>\u0646\u0627\u0645 \u062F\u0633\u062A\u0647 \u062C\u062F\u06CC\u062F</mat-label>\n <input matInput #itemValue />\n </mat-form-field>\n <div class=\"pb-3 ms-2\">\n <button\n mat-icon-button\n (click)=\"saveNode(node, itemValue.value)\"\n class=\"pgh-tree-save-button\"\n >\n <mat-icon svgIcon=\"done\" color=\"primary\"></mat-icon>\n </button>\n </div>\n </mat-tree-node>\n }\n\n <mat-tree-node *matTreeNodeDef=\"let node; when: hasChild\" matTreeNodePadding>\n <button mat-icon-button matTreeNodeToggle [attr.aria-label]=\"'Toggle ' + node.item\">\n <mat-icon\n class=\"mat-icon-rtl-mirror\"\n [svgIcon]=\"treeControl()!.isExpanded(node) ? 'expand_more' : 'chevron_right'\"\n ></mat-icon>\n </button>\n <ng-container\n *ngTemplateOutlet=\"nodeTemplate; context: { node: node, isParent: true }\"\n ></ng-container>\n @if (canAddChildNode()) {\n <button mat-icon-button (click)=\"addNewItem(node)\">\n <mat-icon svgIcon=\"add\"></mat-icon>\n </button>\n }\n </mat-tree-node>\n\n <ng-template #contentTemplate let-node>\n <div class=\"d-flex align-items-center\">\n <div>\n @if (customNodeItemTemplate) {\n <ng-template\n [ngTemplateOutlet]=\"customNodeItemTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: flatNodeToNestedNode(node) }\"\n ></ng-template>\n } @else {\n {{ node.item }}\n }\n </div>\n <div>\n @if (customElementTemplate) {\n <ng-template\n [ngTemplateOutlet]=\"customElementTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: flatNodeToNestedNode(node) }\"\n ></ng-template>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #nodeTemplate let-node=\"node\" let-isParent=\"isParent\">\n @if (selectable()) {\n <mat-checkbox\n [checked]=\"isParent ? descendantsAllSelected(node) : checklistSelection().isSelected(node)\"\n [indeterminate]=\"isParent ? descendantsPartiallySelected(node) : null\"\n (change)=\"\n isParent ? parentItemSelectionToggle(node) : leafItemSelectionToggle(node);\n storeSelectedNodes()\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"contentTemplate; context: { $implicit: node }\"\n ></ng-container>\n </mat-checkbox>\n } @else {\n <ng-container *ngTemplateOutlet=\"contentTemplate; context: { $implicit: node }\"></ng-container>\n }\n </ng-template>\n</mat-tree>\n", styles: [".pgh-tree-save-button{height:50px}\n"], dependencies: [{ kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "directive", type: i4.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "directive", type: i4.MatTreeNodePadding, selector: "[matTreeNodePadding]", inputs: ["matTreeNodePadding", "matTreeNodePaddingIndent"] }, { kind: "directive", type: i4.MatTreeNodeToggle, selector: "[matTreeNodeToggle]", inputs: ["matTreeNodeToggleRecursive"] }, { kind: "component", type: i4.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i4.MatTreeNode, selector: "mat-tree-node", inputs: ["tabIndex", "disabled"], outputs: ["activation", "expandedChange"], exportAs: ["matTreeNode"] }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i8.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghTreeComponent, decorators: [{
type: Component,
args: [{ selector: 'pgh-tree', providers: [PghTreeService], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<mat-tree [dataSource]=\"dataSource()!\" [treeControl]=\"treeControl()!\">\n <mat-tree-node *matTreeNodeDef=\"let node\" matTreeNodeToggle matTreeNodePadding>\n <button mat-icon-button disabled></button>\n <ng-container\n *ngTemplateOutlet=\"nodeTemplate; context: { node: node, isParent: false }\"\n ></ng-container>\n </mat-tree-node>\n\n @if (canAddChildNode()) {\n <mat-tree-node *matTreeNodeDef=\"let node; when: hasNoContent\" matTreeNodePadding>\n <button mat-icon-button disabled></button>\n <mat-form-field appearance=\"fill\">\n <mat-label>\u0646\u0627\u0645 \u062F\u0633\u062A\u0647 \u062C\u062F\u06CC\u062F</mat-label>\n <input matInput #itemValue />\n </mat-form-field>\n <div class=\"pb-3 ms-2\">\n <button\n mat-icon-button\n (click)=\"saveNode(node, itemValue.value)\"\n class=\"pgh-tree-save-button\"\n >\n <mat-icon svgIcon=\"done\" color=\"primary\"></mat-icon>\n </button>\n </div>\n </mat-tree-node>\n }\n\n <mat-tree-node *matTreeNodeDef=\"let node; when: hasChild\" matTreeNodePadding>\n <button mat-icon-button matTreeNodeToggle [attr.aria-label]=\"'Toggle ' + node.item\">\n <mat-icon\n class=\"mat-icon-rtl-mirror\"\n [svgIcon]=\"treeControl()!.isExpanded(node) ? 'expand_more' : 'chevron_right'\"\n ></mat-icon>\n </button>\n <ng-container\n *ngTemplateOutlet=\"nodeTemplate; context: { node: node, isParent: true }\"\n ></ng-container>\n @if (canAddChildNode()) {\n <button mat-icon-button (click)=\"addNewItem(node)\">\n <mat-icon svgIcon=\"add\"></mat-icon>\n </button>\n }\n </mat-tree-node>\n\n <ng-template #contentTemplate let-node>\n <div class=\"d-flex align-items-center\">\n <div>\n @if (customNodeItemTemplate) {\n <ng-template\n [ngTemplateOutlet]=\"customNodeItemTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: flatNodeToNestedNode(node) }\"\n ></ng-template>\n } @else {\n {{ node.item }}\n }\n </div>\n <div>\n @if (customElementTemplate) {\n <ng-template\n [ngTemplateOutlet]=\"customElementTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: flatNodeToNestedNode(node) }\"\n ></ng-template>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #nodeTemplate let-node=\"node\" let-isParent=\"isParent\">\n @if (selectable()) {\n <mat-checkbox\n [checked]=\"isParent ? descendantsAllSelected(node) : checklistSelection().isSelected(node)\"\n [indeterminate]=\"isParent ? descendantsPartiallySelected(node) : null\"\n (change)=\"\n isParent ? parentItemSelectionToggle(node) : leafItemSelectionToggle(node);\n storeSelectedNodes()\n \"\n >\n <ng-container\n *ngTemplateOutlet=\"contentTemplate; context: { $implicit: node }\"\n ></ng-container>\n </mat-checkbox>\n } @else {\n <ng-container *ngTemplateOutlet=\"contentTemplate; context: { $implicit: node }\"></ng-container>\n }\n </ng-template>\n</mat-tree>\n", styles: [".pgh-tree-save-button{height:50px}\n"] }]
}], ctorParameters: () => [{ type: PghTreeService }], propDecorators: { customNodeItemTemplate: [{
type: ContentChild,
args: ['customNodeItemTemplate']
}], customElementTemplate: [{
type: ContentChild,
args: ['customElementTemplate']
}] } });
class PghTreeModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghTreeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.13", ngImport: i0, type: PghTreeModule, declarations: [PghTreeComponent], imports: [CommonModule,
MatCheckboxModule,
MatTreeModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule], exports: [PghTreeComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghTreeModule, imports: [CommonModule,
MatCheckboxModule,
MatTreeModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghTreeModule, decorators: [{
type: NgModule,
args: [{
declarations: [PghTreeComponent],
imports: [
CommonModule,
MatCheckboxModule,
MatTreeModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
],
exports: [PghTreeComponent],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { PghTreeComponent, PghTreeModule };
//# sourceMappingURL=tapsellorg-angular-material-library-src-lib-tree.mjs.map