UNPKG

@tapsellorg/angular-material-library

Version:

Angular library for Tapsell

364 lines (356 loc) 25.2 kB
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