diff --git a/npm/ng-packs/nx/ng-packs/angular.json b/npm/ng-packs/nx/ng-packs/angular.json
index c5e3310001..30bbb732d2 100644
--- a/npm/ng-packs/nx/ng-packs/angular.json
+++ b/npm/ng-packs/nx/ng-packs/angular.json
@@ -42,6 +42,47 @@
}
}
},
+ "components": {
+ "projectType": "library",
+ "root": "packages/components",
+ "sourceRoot": "packages/components/src",
+ "prefix": "abp",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-ng-packagr:build",
+ "options": {
+ "tsConfig": "packages/components/tsconfig.lib.json",
+ "project": "packages/components/ng-package.json"
+ },
+ "configurations": {
+ "production": {
+ "tsConfig": "packages/components/tsconfig.lib.prod.json"
+ },
+ "development": {
+ "tsConfig": "packages/components/tsconfig.lib.json"
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "test": {
+ "builder": "@nrwl/jest:jest",
+ "outputs": ["coverage/packages/components"],
+ "options": {
+ "jestConfig": "packages/components/jest.config.js",
+ "passWithNoTests": true
+ }
+ },
+ "lint": {
+ "builder": "@nrwl/linter:eslint",
+ "options": {
+ "lintFilePatterns": [
+ "packages/components/src/**/*.ts",
+ "packages/components/src/**/*.html"
+ ]
+ }
+ }
+ }
+ },
"core": {
"projectType": "library",
"root": "packages/core",
diff --git a/npm/ng-packs/nx/ng-packs/nx.json b/npm/ng-packs/nx/ng-packs/nx.json
index 63c85f8a6d..db5d09d7be 100644
--- a/npm/ng-packs/nx/ng-packs/nx.json
+++ b/npm/ng-packs/nx/ng-packs/nx.json
@@ -35,6 +35,10 @@
"tags": [],
"implicitDependencies": ["core", "theme-shared"]
},
+ "components": {
+ "tags": [],
+ "implicitDependencies": ["core", "theme-shared"]
+ },
"core": {
"tags": []
},
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/.eslintrc.json b/npm/ng-packs/nx/ng-packs/packages/components/.eslintrc.json
new file mode 100644
index 0000000000..9c51584494
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/.eslintrc.json
@@ -0,0 +1,36 @@
+{
+ "extends": ["../../.eslintrc.json"],
+ "ignorePatterns": ["!**/*"],
+ "overrides": [
+ {
+ "files": ["*.ts"],
+ "extends": [
+ "plugin:@nrwl/nx/angular",
+ "plugin:@angular-eslint/template/process-inline-templates"
+ ],
+ "rules": {
+ "@angular-eslint/directive-selector": [
+ "error",
+ {
+ "type": "attribute",
+ "prefix": "abp",
+ "style": "camelCase"
+ }
+ ],
+ "@angular-eslint/component-selector": [
+ "error",
+ {
+ "type": "element",
+ "prefix": "abp",
+ "style": "kebab-case"
+ }
+ ]
+ }
+ },
+ {
+ "files": ["*.html"],
+ "extends": ["plugin:@nrwl/nx/angular-template"],
+ "rules": {}
+ }
+ ]
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/README.md b/npm/ng-packs/nx/ng-packs/packages/components/README.md
new file mode 100644
index 0000000000..283812b9e0
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/README.md
@@ -0,0 +1,3 @@
+
@abp/ng.components
+
+[docs.abp.io](https://docs.abp.io)
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/jest.config.js b/npm/ng-packs/nx/ng-packs/packages/components/jest.config.js
new file mode 100644
index 0000000000..82ce530095
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/jest.config.js
@@ -0,0 +1,20 @@
+module.exports = {
+ displayName: 'components',
+ preset: '../../jest.preset.js',
+ setupFilesAfterEnv: ['/src/test-setup.ts'],
+ globals: {
+ 'ts-jest': {
+ tsconfig: '/tsconfig.spec.json',
+ stringifyContentPathRegex: '\\.(html|svg)$',
+ },
+ },
+ coverageDirectory: '../../coverage/packages/components',
+ transform: {
+ '^.+\\.(ts|js|html)$': 'jest-preset-angular',
+ },
+ snapshotSerializers: [
+ 'jest-preset-angular/build/serializers/no-ng-attributes',
+ 'jest-preset-angular/build/serializers/ng-snapshot',
+ 'jest-preset-angular/build/serializers/html-comment',
+ ],
+};
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/ng-package.json b/npm/ng-packs/nx/ng-packs/packages/components/ng-package.json
new file mode 100644
index 0000000000..a5343f978a
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/ng-package.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../dist/packages/components",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ },
+ "allowedNonPeerDependencies": ["ng-zorro-antd"]
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/package.json b/npm/ng-packs/nx/ng-packs/packages/components/package.json
new file mode 100644
index 0000000000..767261b15a
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@abp/ng.components",
+ "version": "4.4.0",
+ "homepage": "https://abp.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/abpframework/abp.git"
+ },
+ "peerDependencies": {
+ "@abp/ng.core": ">=4.4.0",
+ "@abp/ng.theme.shared": ">=4.4.0",
+ "@ng-bootstrap/ng-bootstrap": ">=6.0.0"
+ },
+ "dependencies": {
+ "ng-zorro-antd": "^11.0.0",
+ "tslib": "^2.0.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/page/ng-package.json b/npm/ng-packs/nx/ng-packs/packages/components/page/ng-package.json
new file mode 100644
index 0000000000..530fd56dca
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/page/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../../dist/packages/components/page",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ }
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/page/src/page-part.directive.ts b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page-part.directive.ts
new file mode 100644
index 0000000000..c228189e0f
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page-part.directive.ts
@@ -0,0 +1,95 @@
+import {
+ Directive,
+ TemplateRef,
+ ViewContainerRef,
+ Input,
+ InjectionToken,
+ Optional,
+ Inject,
+ OnInit,
+ OnDestroy,
+ Injector,
+ OnChanges,
+ SimpleChanges,
+ SimpleChange,
+} from '@angular/core';
+import { Observable, Subscription, of } from 'rxjs';
+
+export interface PageRenderStrategy {
+ shouldRender(type?: string): boolean | Observable;
+ onInit?(type?: string, injector?: Injector, context?: any): void;
+ onDestroy?(type?: string, injector?: Injector, context?: any): void;
+ onContextUpdate?(change?: SimpleChange): void;
+}
+
+export const PAGE_RENDER_STRATEGY = new InjectionToken('PAGE_RENDER_STRATEGY');
+
+@Directive({ selector: '[abpPagePart]' })
+export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
+ hasRendered = false;
+ type: string;
+ subscription: Subscription;
+
+ @Input('abpPagePartContext') context: any;
+ @Input() set abpPagePart(type: string) {
+ this.type = type;
+ this.createRenderStream(type);
+ }
+
+ render = (shouldRender: boolean) => {
+ if (shouldRender && !this.hasRendered) {
+ this.viewContainer.createEmbeddedView(this.templateRef);
+ this.hasRendered = true;
+ } else if (!shouldRender && this.hasRendered) {
+ this.viewContainer.clear();
+ this.hasRendered = false;
+ }
+ };
+
+ constructor(
+ private templateRef: TemplateRef,
+ private viewContainer: ViewContainerRef,
+ @Optional() @Inject(PAGE_RENDER_STRATEGY) private renderLogic: PageRenderStrategy,
+ private injector: Injector,
+ ) {}
+
+ ngOnChanges({ context }: SimpleChanges): void {
+ if (this.renderLogic?.onContextUpdate) {
+ this.renderLogic.onContextUpdate(context);
+ }
+ }
+
+ ngOnInit() {
+ if (this.renderLogic?.onInit) {
+ this.renderLogic.onInit(this.type, this.injector, this.context);
+ }
+ }
+
+ ngOnDestroy() {
+ this.clearSubscription();
+
+ if (this.renderLogic?.onDestroy) {
+ this.renderLogic.onDestroy(this.type, this.injector, this.context);
+ }
+ }
+
+ shouldRender(type: string) {
+ if (this.renderLogic) {
+ const willRender = this.renderLogic.shouldRender(type);
+ return willRender instanceof Observable ? willRender : of(willRender);
+ }
+ return of(true);
+ }
+
+ protected createRenderStream(type: string) {
+ this.clearSubscription();
+
+ this.subscription = this.shouldRender(type).subscribe(this.render);
+ }
+
+ protected clearSubscription() {
+ if (this.subscription) {
+ this.subscription.unsubscribe();
+ }
+ }
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/page/src/page-parts.component.ts b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page-parts.component.ts
new file mode 100644
index 0000000000..8438593268
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page-parts.component.ts
@@ -0,0 +1,34 @@
+import { Component, ViewEncapsulation } from '@angular/core';
+
+export enum PageParts {
+ title = 'PageTitleContainerComponent',
+ breadcrumb = 'PageBreadcrumbContainerComponent',
+ toolbar = 'PageToolbarContainerComponent',
+}
+
+@Component({
+ selector: 'abp-page-title-container',
+ template: `
+
+ `,
+ encapsulation: ViewEncapsulation.None,
+})
+export class PageTitleContainerComponent {}
+
+@Component({
+ selector: 'abp-page-breadcrumb-container',
+ template: `
+
+ `,
+ encapsulation: ViewEncapsulation.None,
+})
+export class PageBreadcrumbContainerComponent {}
+
+@Component({
+ selector: 'abp-page-toolbar-container',
+ template: `
+
+ `,
+ encapsulation: ViewEncapsulation.None,
+})
+export class PageToolbarContainerComponent {}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.component.html b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.component.html
new file mode 100644
index 0000000000..fe5aff2e58
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.component.ts b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.component.ts
new file mode 100644
index 0000000000..24b6d18ccf
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.component.ts
@@ -0,0 +1,51 @@
+import { Component, Input, ViewEncapsulation, ContentChild } from '@angular/core';
+import {
+ PageTitleContainerComponent,
+ PageBreadcrumbContainerComponent,
+ PageToolbarContainerComponent,
+ PageParts,
+} from './page-parts.component';
+
+@Component({
+ selector: 'abp-page',
+ templateUrl: './page.component.html',
+ encapsulation: ViewEncapsulation.None,
+})
+export class PageComponent {
+ @Input() title: string;
+
+ toolbarVisible = false;
+ _toolbarData: any;
+ @Input('toolbar') set toolbarData(val: any) {
+ this._toolbarData = val;
+ this.toolbarVisible = true;
+ }
+
+ get toolbarData() {
+ return this._toolbarData;
+ }
+
+ @Input('breadcrumb') breadcrumbVisible = true;
+
+ pageParts = {
+ title: PageParts.title,
+ breadcrumb: PageParts.breadcrumb,
+ toolbar: PageParts.toolbar,
+ };
+
+ @ContentChild(PageTitleContainerComponent) customTitle: PageTitleContainerComponent;
+ @ContentChild(PageBreadcrumbContainerComponent)
+ customBreadcrumb: PageBreadcrumbContainerComponent;
+ @ContentChild(PageToolbarContainerComponent) customToolbar: PageToolbarContainerComponent;
+
+ get shouldRenderRow() {
+ return !!(
+ this.title ||
+ this.toolbarVisible ||
+ this.breadcrumbVisible ||
+ this.customTitle ||
+ this.customBreadcrumb ||
+ this.customToolbar
+ );
+ }
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.module.ts b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.module.ts
new file mode 100644
index 0000000000..3f12543be7
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/page/src/page.module.ts
@@ -0,0 +1,27 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions';
+import { ThemeSharedModule } from '@abp/ng.theme.shared';
+import { CoreModule } from '@abp/ng.core';
+import { PageComponent } from './page.component';
+import {
+ PageTitleContainerComponent,
+ PageBreadcrumbContainerComponent,
+ PageToolbarContainerComponent,
+} from './page-parts.component';
+import { PagePartDirective } from './page-part.directive';
+
+const exportedDeclarations = [
+ PageComponent,
+ PageTitleContainerComponent,
+ PageBreadcrumbContainerComponent,
+ PageToolbarContainerComponent,
+ PagePartDirective,
+];
+
+@NgModule({
+ declarations: [...exportedDeclarations],
+ imports: [CommonModule, UiExtensionsModule, CoreModule, ThemeSharedModule],
+ exports: [...exportedDeclarations],
+})
+export class PageModule {}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/page/src/public-api.ts b/npm/ng-packs/nx/ng-packs/packages/components/page/src/public-api.ts
new file mode 100644
index 0000000000..980f8de374
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/page/src/public-api.ts
@@ -0,0 +1,4 @@
+export * from './page.module';
+export * from './page-part.directive';
+export * from './page-parts.component';
+export * from './page.component';
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/src/public-api.ts b/npm/ng-packs/nx/ng-packs/packages/components/src/public-api.ts
new file mode 100644
index 0000000000..2302a05a8c
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/src/public-api.ts
@@ -0,0 +1,5 @@
+/*
+ * Public API Surface of components
+ */
+
+export {};
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/src/test-setup.ts b/npm/ng-packs/nx/ng-packs/packages/components/src/test-setup.ts
new file mode 100644
index 0000000000..1100b3e8a6
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/src/test-setup.ts
@@ -0,0 +1 @@
+import 'jest-preset-angular/setup-jest';
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/ng-package.json b/npm/ng-packs/nx/ng-packs/packages/components/tree/ng-package.json
new file mode 100644
index 0000000000..0a2a39f416
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../../dist/packages/components/tree",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ }
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.html b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.html
new file mode 100644
index 0000000000..4f4aaf472d
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.html
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ node.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.scss b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.scss
new file mode 100644
index 0000000000..07ca018f0d
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.scss
@@ -0,0 +1,38 @@
+.ant-tree {
+ color: inherit;
+
+ .ant-tree-node-content-wrapper.ant-tree-node-selected {
+ background-color: transparent;
+ }
+
+ .ant-tree-switcher {
+ line-height: 17px;
+ }
+
+ .ant-tree-node-content-wrapper {
+ width: 100%;
+ padding: 0;
+ }
+
+ .node-wrapper {
+ width: 100%;
+ position: relative;
+ display: inline-block;
+ margin: 0;
+ padding: 0 5px;
+ line-height: 24px;
+ text-decoration: none;
+ vertical-align: top;
+ border-radius: 2px;
+ cursor: pointer;
+ padding-left: 8px;
+ border: 1px solid transparent;
+ }
+
+ .ellipsis {
+ position: absolute;
+ right: 8px;
+ top: 1px;
+ cursor: pointer;
+ }
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.ts b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.ts
new file mode 100644
index 0000000000..3f6179a5b2
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/components/tree.component.ts
@@ -0,0 +1,79 @@
+import {
+ Component,
+ ContentChild,
+ EventEmitter,
+ Input,
+ Output,
+ TemplateRef,
+ ViewEncapsulation,
+} from '@angular/core';
+import { NzFormatEmitEvent, NzFormatBeforeDropEvent } from 'ng-zorro-antd/tree';
+import { of } from 'rxjs';
+import { TreeNodeTemplateDirective } from '../templates/tree-node-template.directive';
+import { ExpandedIconTemplateDirective } from '../templates/expanded-icon-template.directive';
+import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
+
+export type DropEvent = NzFormatEmitEvent & { pos: number };
+
+@Component({
+ selector: 'abp-tree',
+ templateUrl: 'tree.component.html',
+ styleUrls: [
+ '../../../../../../node_modules/ng-zorro-antd/tree/style/index.min.css',
+ 'tree.component.scss',
+ ],
+ encapsulation: ViewEncapsulation.None,
+})
+export class TreeComponent {
+ dropPosition: number;
+
+ dropdowns = {} as { [key: string]: NgbDropdown };
+
+ @ContentChild('menu') menu: TemplateRef;
+ @ContentChild(TreeNodeTemplateDirective) customNodeTemplate: TreeNodeTemplateDirective;
+ @ContentChild(ExpandedIconTemplateDirective) expandedIconTemplate: ExpandedIconTemplateDirective;
+ @Output() readonly checkedKeysChange = new EventEmitter();
+ @Output() readonly expandedKeysChange = new EventEmitter();
+ @Output() readonly selectedNodeChange = new EventEmitter();
+ @Output() readonly dropOver = new EventEmitter();
+ @Input() noAnimation = true;
+ @Input() draggable: boolean;
+ @Input() checkable: boolean;
+ @Input() checkStrictly: boolean;
+ @Input() checkedKeys = [];
+ @Input() nodes = [];
+ @Input() expandedKeys: string[] = [];
+ @Input() selectedNode: any;
+ @Input() isNodeSelected = node => this.selectedNode?.id === node.key;
+ @Input() beforeDrop = (event: NzFormatBeforeDropEvent) => {
+ this.dropPosition = event.pos;
+ return of(false);
+ };
+
+ onSelectedNodeChange(node) {
+ this.selectedNode = node.origin.entity;
+ this.selectedNodeChange.emit(node.origin.entity);
+ }
+
+ onCheckboxChange(event) {
+ this.checkedKeys = [...event.keys];
+ this.checkedKeysChange.emit(event.keys);
+ }
+
+ onExpandedKeysChange(event) {
+ this.expandedKeys = [...event.keys];
+ this.expandedKeysChange.emit(event.keys);
+ }
+
+ onDrop(event: DropEvent) {
+ event.event.stopPropagation();
+ event.event.preventDefault();
+ event.pos = this.dropPosition;
+
+ this.dropOver.emit(event);
+ }
+
+ initDropdown(key: string, dropdown: NgbDropdown) {
+ this.dropdowns[key] = dropdown;
+ }
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts
new file mode 100644
index 0000000000..35706bd25f
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts
@@ -0,0 +1,8 @@
+import { Directive, TemplateRef } from '@angular/core';
+
+@Directive({
+ selector: '[abpTreeExpandedIconTemplate],[abp-tree-expanded-icon-template]',
+})
+export class ExpandedIconTemplateDirective {
+ constructor(public template: TemplateRef) {}
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts
new file mode 100644
index 0000000000..56c66af04b
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts
@@ -0,0 +1,8 @@
+import { Directive, TemplateRef } from '@angular/core';
+
+@Directive({
+ selector: '[abpTreeNodeTemplate],[abp-tree-node-template]',
+})
+export class TreeNodeTemplateDirective {
+ constructor(public template: TemplateRef) {}
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/tree.module.ts b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/tree.module.ts
new file mode 100644
index 0000000000..2013c37588
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/tree.module.ts
@@ -0,0 +1,19 @@
+import { CoreModule } from '@abp/ng.core';
+import { NgModule } from '@angular/core';
+import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
+import { NzNoAnimationModule } from 'ng-zorro-antd/core/no-animation';
+import { NzTreeModule } from 'ng-zorro-antd/tree';
+import { TreeComponent } from './components/tree.component';
+import { ExpandedIconTemplateDirective } from './templates/expanded-icon-template.directive';
+import { TreeNodeTemplateDirective } from './templates/tree-node-template.directive';
+
+const templates = [TreeNodeTemplateDirective, ExpandedIconTemplateDirective];
+
+const exported = [...templates, TreeComponent];
+
+@NgModule({
+ imports: [CoreModule, NzTreeModule, NgbDropdownModule, NzNoAnimationModule],
+ exports: [...exported],
+ declarations: [...exported],
+})
+export class TreeModule {}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts
new file mode 100644
index 0000000000..d5d32f0146
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts
@@ -0,0 +1,112 @@
+export abstract class BaseNode {
+ name?: string;
+ displayName?: string;
+
+ constructor(public id: string, public parentId: string | null) {}
+}
+
+class TreeNode extends BaseNode {
+ title: string | undefined;
+ key: string;
+ icon: string | null = null;
+ children: TreeNode[] = [];
+ isLeaf = true;
+ checked = false;
+ selected = false;
+ expanded = false;
+ selectable = true;
+ disabled = false;
+ disableCheckbox = false;
+ parentNode?: TreeNode | null;
+
+ constructor(public entity: T, private nameResolver = ent => ent.displayName || ent.name) {
+ super(entity.id, entity.parentId);
+ this.key = entity.id;
+ this.title = nameResolver(entity);
+ }
+}
+
+export class TreeAdapter {
+ private tree: TreeNode[];
+
+ constructor(private list: T[] = []) {
+ this.tree = createTreeFromList(this.list);
+ }
+
+ getList() {
+ return this.list;
+ }
+
+ getTree() {
+ return this.tree;
+ }
+
+ handleDrop({ key, parentNode }: TreeNode) {
+ const index = this.list.findIndex(({ id }) => id === key);
+ this.list[index].parentId = parentNode ? parentNode.key : null;
+ this.tree = createTreeFromList(this.list);
+ }
+
+ handleRemove({ key }: TreeNode) {
+ this.updateTreeFromList(this.list.filter(item => item.id !== key));
+ }
+
+ handleUpdate({ key, children }: { key: string; children: T[] }) {
+ /**
+ * When we need to update a node with new children, first we need to remove any descendant nodes.
+ * If we remove immediate children and create a new tree, any other descendant nodes will be removed
+ * and we won't need to recursively remove sub children.
+ * Then, you simply add back the new children and create a new tree.
+ */
+ const listWithDescendantNodesRemoved = this.updateTreeFromList(
+ this.list.filter(item => item.parentId !== key),
+ );
+ this.updateTreeFromList(listWithDescendantNodesRemoved.concat(children));
+ }
+
+ updateTreeFromList(list: T[]) {
+ this.tree = createTreeFromList(list);
+ this.list = createListFromTree(this.tree);
+ return this.list;
+ }
+}
+
+// UTILITY FUNCTIONS
+
+function createTreeFromList(list: T[]): TreeNode[] {
+ const map = createMapFromList(list);
+ const tree: TreeNode[] = [];
+
+ list.forEach(row => {
+ const parentId = row.parentId;
+ const node = map.get(row.id);
+ if (parentId) {
+ const parent = map.get(parentId);
+ if (!parent) return;
+ parent.children.push(node);
+ parent.isLeaf = false;
+ } else {
+ tree.push(node);
+ }
+ });
+
+ return tree;
+}
+
+function createListFromTree(tree: TreeNode[], list: T[] = []): T[] {
+ tree.forEach(node => {
+ list.push({ ...node.entity, parentId: node.parentId });
+ if (node.children) createListFromTree(node.children, list);
+ });
+
+ return list;
+}
+
+function createMapFromList(
+ list: T[],
+ map = new Map>(),
+): Map> {
+ list.forEach(row => map.set(row.id, new TreeNode(row)));
+
+ return map;
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tree/src/public-api.ts b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/public-api.ts
new file mode 100644
index 0000000000..1f3dd48e00
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tree/src/public-api.ts
@@ -0,0 +1,5 @@
+export * from './lib/tree.module';
+export * from './lib/components/tree.component';
+export * from './lib/utils/nz-tree-adapter';
+export * from './lib/templates/tree-node-template.directive';
+export * from './lib/templates/expanded-icon-template.directive';
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.json b/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.json
new file mode 100644
index 0000000000..62ebbd9464
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
+ }
+ ]
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.lib.json b/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.lib.json
new file mode 100644
index 0000000000..bbcc12b1c7
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.lib.json
@@ -0,0 +1,14 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "target": "es2015",
+ "declaration": true,
+ "declarationMap": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": ["dom", "es2018"]
+ },
+ "exclude": ["src/test-setup.ts", "**/*.spec.ts"],
+ "include": ["**/*.ts"]
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.lib.prod.json b/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.lib.prod.json
new file mode 100644
index 0000000000..331d0ecb05
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.lib.prod.json
@@ -0,0 +1,11 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "extends": "./tsconfig.lib.json",
+ "compilerOptions": {
+ "declarationMap": false
+ },
+ "angularCompilerOptions": {
+ "compilationMode": "partial",
+ "enableIvy": false
+ }
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.spec.json b/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.spec.json
new file mode 100644
index 0000000000..cfff29a544
--- /dev/null
+++ b/npm/ng-packs/nx/ng-packs/packages/components/tsconfig.spec.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"]
+ },
+ "files": ["src/test-setup.ts"],
+ "include": ["**/*.spec.ts", "**/*.d.ts"]
+}
diff --git a/npm/ng-packs/nx/ng-packs/packages/theme-shared/testing/src/public-api.ts b/npm/ng-packs/nx/ng-packs/packages/theme-shared/testing/src/public-api.ts
index 14355a8a28..dc639b1462 100644
--- a/npm/ng-packs/nx/ng-packs/packages/theme-shared/testing/src/public-api.ts
+++ b/npm/ng-packs/nx/ng-packs/packages/theme-shared/testing/src/public-api.ts
@@ -1 +1,3 @@
+import * as ThemeSharedTesting from './lib/models';
export * from './lib/theme-shared-testing.module';
+export { ThemeSharedTesting };
diff --git a/npm/ng-packs/nx/ng-packs/tsconfig.base.json b/npm/ng-packs/nx/ng-packs/tsconfig.base.json
index 3b7724759e..cee7a51cd2 100644
--- a/npm/ng-packs/nx/ng-packs/tsconfig.base.json
+++ b/npm/ng-packs/nx/ng-packs/tsconfig.base.json
@@ -16,9 +16,17 @@
"baseUrl": ".",
"paths": {
"@abp/ng.core": ["dist/packages/core"],
+ "@abp/ng.core/locale": ["dist/packages/core/locale"],
+ "@abp/ng.core/testing": ["dist/packages/core/testing"],
"@abp/ng.theme.shared": ["dist/packages/theme-shared"],
+ "@abp/ng.theme.shared/extensions": ["dist/packages/theme-shared/extensions"],
+ "@abp/ng.theme.shared/testing": ["dist/packages/theme-shared/testing"],
+ "@abp/ng.components": ["dist/packages/components"],
+ "@abp/ng.components/page": ["dist/packages/components/page"],
+ "@abp/ng.components/tree": ["dist/packages/components/tree"],
"@abp/ng.account.core": ["dist/packages/account-core"],
- "@abp/ng.theme.basic": ["dist/packages/theme-basic"]
+ "@abp/ng.theme.basic": ["dist/packages/theme-basic"],
+ "@abp/ng.theme.basic/testing": ["dist/packages/theme-basic/testing"]
}
},
"exclude": ["node_modules", "tmp"]