diff --git a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.html b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.html index 84c3928..aec8745 100644 --- a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.html +++ b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.html @@ -1,12 +1,17 @@ - +
+ Behörigheter

Här kan du ändra personalkontots behörigheter.

-

Tjänster

Välj de tjänster du vill ge personalen tillgång till.

@@ -32,6 +36,7 @@ [afSelectItems]="selectableTjansterFormItems" [afDisableValidStyle]="true" [afInvalid]="tjansterFormControl.invalid && tjansterFormControl.touched" + [afId]="tjansterElementId" (afOnChange)="toggleTjanst()" > Du måste välja en eller flera tjänster för att kunna välja utförande verksamheter.

- + + - + + + + +
diff --git a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.ts b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.ts index 18c1c2c..db1095f 100644 --- a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.ts +++ b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/edit-employee-form/edit-employee-form.component.ts @@ -3,12 +3,14 @@ import { FormSelectItem } from '@af/digi-ng/_form/form-select'; import { ChangeDetectionStrategy, Component, + ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, + ViewChild, } from '@angular/core'; import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; import { RoleEnum } from '@msfa-enums/role.enum'; @@ -19,13 +21,12 @@ import { Role } from '@msfa-models/role.model'; import { Tjanst } from '@msfa-models/tjanst.model'; import { UtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model'; import { UtforandeVerksamheterService } from '@msfa-services/utforande-verksamheter/utforande-verksamheter.service'; -import { - TreeNode, - TreeNodesSelectorService, -} from '@msfa-shared/components/tree-nodes-selector/services/tree-nodes-selector.service'; +import { ValidationErrorLink } from '@msfa-shared/components/error-list/error-list.component'; +import { TreeNodesSelectorService } from '@msfa-shared/components/tree-nodes-selector/services/tree-nodes-selector.service'; import { EmailValidator } from '@msfa-utils/validators/email.validator'; +import { EmployeeValidator } from '@msfa-utils/validators/employee.validator'; import { RequiredValidator } from '@msfa-utils/validators/required.validator'; -import { TreeNodeValidator } from '@msfa-utils/validators/tree-node.validator'; +import { UUID } from 'angular2-uuid'; import { EmployeeFormService } from '../services/employee-form.service'; @Component({ @@ -35,9 +36,12 @@ import { EmployeeFormService } from '../services/employee-form.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class EditEmployeeFormComponent implements OnInit, OnChanges { + @ViewChild('editEmployeeFormContainer') editEmployeeFormContainer: ElementRef; + @Input() employee: Employee; @Input() availableRoles: Role[]; @Input() availableTjanster: Tjanst[]; + @Input() isLoadingUtforandeVerksamheter = false; @Input() availableUtforandeVerksamheter: UtforandeVerksamhet[]; @Input() errorWhileUpdating: CustomError; @@ -51,14 +55,21 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges { readonly emailFormControlName = 'email'; readonly tjansterFormControlName = 'tjanster'; - readonly utforandeVerksamhetFormControlName = 'utforandeVerksamheter'; - readonly toggleAllUtforandeVerksamhetFormControlName = 'allaUtforandeVerksamheter'; + readonly utforandeVerksamheterFormControlName = 'utforandeVerksamheter'; + readonly selectAllUtforandeVerksamheterFormControlName = 'allaUtforandeVerksamheter'; + readonly utforandeVerksamhetRequiredMessage = 'Välj minst en utförande verksamhet'; + readonly formUuid = UUID.UUID(); + readonly emailElementId = `email-control-${this.formUuid}`; + readonly tjansterElementId = `tjanster-control-${this.formUuid}`; + readonly utforandeVerksamhetElementId = `utforande-verksamhet-control-${this.formUuid}`; + readonly firstValidationErrorLinkId = `validation-error-link-${this.formUuid}`; editEmployeeFormGroup: FormGroup | null = null; displayEditWithoutRolesDialog = false; displayPristineWarning = false; displayRolesDialog = false; + displayErrorSummary = false; selectableTjansterFormItems: Array | null = null; @@ -88,9 +99,11 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges { this.editEmployeeFormGroup.patchValue( Object.fromEntries([ [ - this.utforandeVerksamhetFormControlName, + this.utforandeVerksamheterFormControlName, this.utforandeVerksamheterService.getTreeNodeDataFromUtforandeVerksamheter( - this.availableUtforandeVerksamheter + this.availableUtforandeVerksamheter, + this.employee?.utforandeVerksamheter, + this.employee?.allaUtforandeVerksamheter ), ], ]) @@ -112,23 +125,23 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges { return this.editEmployeeFormGroup.get('roles'); } - get utforandeVerksamhetFormControl(): AbstractControl | undefined { - return this.editEmployeeFormGroup?.get(this.utforandeVerksamhetFormControlName); + get utforandeVerksamheterFormControl(): AbstractControl | undefined { + return this.editEmployeeFormGroup?.get(this.utforandeVerksamheterFormControlName); } - get toggleAllUtforandeVerksamhetFormControl(): AbstractControl | undefined { - return this.editEmployeeFormGroup?.get(this.toggleAllUtforandeVerksamhetFormControlName); + get selectAllUtforandeVerksamheterFormControl(): AbstractControl | undefined { + return this.editEmployeeFormGroup?.get(this.selectAllUtforandeVerksamheterFormControlName); } private updateUtforandeVerksamhetStatus(): void { if (this.availableUtforandeVerksamheter && this.availableUtforandeVerksamheter.length > 0) { - this.utforandeVerksamhetFormControl.enable(); - this.toggleAllUtforandeVerksamhetFormControl.enable(); + this.utforandeVerksamheterFormControl.enable(); + this.selectAllUtforandeVerksamheterFormControl.enable(); return; } - this.utforandeVerksamhetFormControl.disable(); - this.toggleAllUtforandeVerksamhetFormControl.disable(); + this.utforandeVerksamheterFormControl.disable(); + this.selectAllUtforandeVerksamheterFormControl.disable(); } private updateSelectableTjansterFormItems(): void { @@ -143,22 +156,32 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges { ? this.availableTjanster.find(tjanst => tjanst.code === currentTjanst.tjansteKod).tjanstId : null; - this.editEmployeeFormGroup = new FormGroup({ - email: new FormControl(this.employee.email, [RequiredValidator('E-postadress'), EmailValidator()]), - tjanster: new FormControl(tjanstId, [RequiredValidator('Tjänst')]), - roles: this.employeeFormService.getRolesFormGroup(this.availableRoles, this.employee.roles), - utforandeVerksamheter: new FormControl( - this.utforandeVerksamheterService.getTreeNodeDataFromUtforandeVerksamheter(this.availableUtforandeVerksamheter), - [ - TreeNodeValidator.IsValidTreeNode( - this.utforandeVerksamheterService.hasSelectedUtforandeVerksamhet, - 'required', - this.toggleAllUtforandeVerksamhetFormControl + this.editEmployeeFormGroup = new FormGroup( + { + email: new FormControl(this.employee.email, [ + RequiredValidator('E-postadress'), + EmailValidator('e-postadress'), + ]), + tjanster: new FormControl(tjanstId, [RequiredValidator('Tjänst')]), + roles: this.employeeFormService.getRolesFormGroup(this.availableRoles, this.employee.roles), + utforandeVerksamheter: new FormControl( + this.utforandeVerksamheterService.getTreeNodeDataFromUtforandeVerksamheter( + this.availableUtforandeVerksamheter, + this.employee?.utforandeVerksamheter, + this.employee?.allaUtforandeVerksamheter ), - ] - ), - allaUtforandeVerksamheter: new FormControl(this.employee.allaUtforandeVerksamheter), - }); + [] + ), + allaUtforandeVerksamheter: new FormControl(this.employee.allaUtforandeVerksamheter), + }, + { + validators: EmployeeValidator.HasSelectedAtLeastOneUtforandeVerksamhet( + this.utforandeVerksamheterFormControlName, + this.selectAllUtforandeVerksamheterFormControlName, + this.utforandeVerksamheterService.hasSelectedUtforandeVerksamhet + ), + } + ); this.updateUtforandeVerksamhetStatus(); } @@ -172,22 +195,27 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges { } onFormSubmitted(saveWithoutRoles = false): void { - if (!this.editEmployeeFormGroup) { + if (!this.editEmployeeFormGroup || this.isLoadingUtforandeVerksamheter) { return; } + this.displayPristineWarning = this.editEmployeeFormGroup.pristine; + this.editEmployeeFormGroup.markAllAsTouched(); - if (this.editEmployeeFormGroup.invalid) { + if (this.editEmployeeFormGroup.invalid || this.editEmployeeFormGroup.pristine) { + this.displayErrorSummary = true; + setTimeout(() => { + this.focusLinkElement('.error-list__validation-error-link a'); + }); + return; } - if (this.editEmployeeFormGroup.pristine) { - this.displayPristineWarning = true; - return; - } + this.displayErrorSummary = false; const roles = this.employeeFormService.getRolesFromFormGroup(this.rolesFormGroup, this.availableRoles); + if (!roles.length && !saveWithoutRoles) { this.displayEditWithoutRolesDialog = true; return; @@ -204,21 +232,67 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges { RoleEnum.MSFA_Standard, ]), ], - adressIds: this.toggleAllUtforandeVerksamhetFormControl.value + adressIds: this.selectAllUtforandeVerksamheterFormControl.value ? [] : this.utforandeVerksamheterService.getSelectedAdressIdsFromTreeNode( - this.utforandeVerksamhetFormControl?.value + this.utforandeVerksamheterFormControl?.value ), - allaUtforandeVerksamheter: !!this.toggleAllUtforandeVerksamhetFormControl.value, + allaUtforandeVerksamheter: !!this.selectAllUtforandeVerksamheterFormControl.value, }); } - toggleTjanst(): void { - if (this.tjansterFormControl.value) { - this.tjansterSelected.emit( - this.employeeFormService.getSelectedTjanster(this.availableTjanster, +this.tjansterFormControl.value) - ); + getValidationErrorLinks(): ValidationErrorLink[] { + let validationErrorLinks: ValidationErrorLink[] = []; + + if (!this.editEmployeeFormGroup) { + return; } + + if (this.emailFormControl?.errors) { + validationErrorLinks = validationErrorLinks.concat({ + elementId: this.emailElementId, + text: this.emailFormControl?.errors?.message as string, + }); + } + + if (this.tjansterFormControl?.errors) { + validationErrorLinks = validationErrorLinks.concat({ + elementId: this.tjansterElementId, + text: this.tjansterFormControl?.errors?.message as string, + }); + } + + if (this.editEmployeeFormGroup.errors?.noUtforandeVerksamhetSelected) { + validationErrorLinks = validationErrorLinks.concat({ + elementId: this.utforandeVerksamhetElementId, + text: this.utforandeVerksamhetRequiredMessage, + }); + } + + return validationErrorLinks; + } + + focusLinkElement(selector: string): void { + let errorListElement: HTMLElement = null; + let linkElement: HTMLLinkElement = null; + + if (!this.editEmployeeFormContainer || !this.editEmployeeFormContainer.nativeElement) { + return; + } + + errorListElement = this.editEmployeeFormContainer.nativeElement as HTMLElement; + linkElement = errorListElement?.querySelector(selector); + linkElement?.focus(); + } + + toggleTjanst(): void { + if (!this.tjansterFormControl.value) { + return; + } + + this.tjansterSelected.emit( + this.employeeFormService.getSelectedTjanster(this.availableTjanster, +this.tjansterFormControl.value) + ); } openRolesDialog(): void { @@ -229,23 +303,13 @@ export class EditEmployeeFormComponent implements OnInit, OnChanges { this.displayRolesDialog = false; } - toggleAllUtforandeVerksamheter(selectAll: boolean): void { - let treeNode: TreeNode = this.utforandeVerksamhetFormControl.value as TreeNode; - - treeNode = selectAll - ? this.treeNodesSelectorService.getTreeNodeWithAllNodesSelected(treeNode) - : this.treeNodesSelectorService.getTreeNodeWithNoNodesSelected(treeNode); - - this.editEmployeeFormGroup.patchValue(Object.fromEntries([[this.utforandeVerksamhetFormControlName, treeNode]])); - } - updateToggleAllUtforandeVerksamheter(): void { const hasSelectedAllLeafNodes = this.treeNodesSelectorService.hasSelectedAllLeafNodes( - this.utforandeVerksamhetFormControl.value + this.utforandeVerksamheterFormControl.value ); this.editEmployeeFormGroup.patchValue( - Object.fromEntries([[this.toggleAllUtforandeVerksamhetFormControlName, hasSelectedAllLeafNodes]]) + Object.fromEntries([[this.selectAllUtforandeVerksamheterFormControlName, hasSelectedAllLeafNodes]]) ); } diff --git a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.component.html b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.component.html index 41bb83f..6dd7793 100644 --- a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.component.html +++ b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.component.html @@ -1,6 +1,6 @@ -
+

Redigera personalkonto

@@ -13,41 +13,46 @@ Ta bort konto -->
- -
-

Personuppgifter

-
-
Förnamn
-
{{employee.firstName}}
-
-
-
Efternamn
-
{{employee.lastName}}
-
-
-
Personnummer
-
- -
-
-
-
- -
+ +
+

Personuppgifter

+
+
Förnamn
+
{{employee.firstName}}
+
+
+
Efternamn
+
{{employee.lastName}}
+
+
+
Personnummer
+
+ +
+
+
+
+ +
+
+ + +
diff --git a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.component.ts b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.component.ts index 8392fae..ae63d8e 100644 --- a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.component.ts +++ b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.component.ts @@ -10,8 +10,8 @@ import { EmployeeService } from '@msfa-services/api/employee.service'; import { TjanstService } from '@msfa-services/api/tjanst.service'; import { RoleService } from '@msfa-services/role.service'; import { UtforandeVerksamheterService } from '@msfa-services/utforande-verksamheter/utforande-verksamheter.service'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { filter, switchMap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, of } from 'rxjs'; +import { catchError, mapTo, startWith, switchMap } from 'rxjs/operators'; @Component({ selector: 'msfa-employee-form', @@ -23,16 +23,18 @@ export class EmployeeFormComponent implements OnInit { private _employeeId$ = new BehaviorSubject(this.activatedRoute.snapshot.params['employeeId']); private _selectedTjanstIds$ = new BehaviorSubject(null); private _errorWhileUpdating$ = new BehaviorSubject(null); - errorWhileUpdating$: Observable = this._errorWhileUpdating$.asObservable(); - employee$ = this.employeeService.employee$; - tjanster$: Observable = this.tjanstService.tjanster$; + availableUtforandeVerksamheter$: Observable = this._selectedTjanstIds$.pipe( - filter(selectedTjanstIds => !!selectedTjanstIds?.length), switchMap(selectedTjanstIds => this.utforandeVerksamheterService.fetchUtforandeVerksamheter$(selectedTjanstIds)) ); + errorWhileUpdating$: Observable = this._errorWhileUpdating$.asObservable(); + employee$ = this.employeeService.employee$; + tjanster$: Observable = this.tjanstService.tjanster$; availableRoles: Role[] = this.roleService.allRoles; + isLoadingUtforandeVerksamheter$: Observable; + constructor( private employeeService: EmployeeService, private roleService: RoleService, @@ -68,6 +70,11 @@ export class EmployeeFormComponent implements OnInit { setupAvailableUtforandeVerksamheter(selectedTjanster: Tjanst[]): void { this._selectedTjanstIds$.next(selectedTjanster.map(tjanst => tjanst.tjanstId)); + this.isLoadingUtforandeVerksamheter$ = this.availableUtforandeVerksamheter$.pipe( + mapTo(false), + catchError(() => of(false)), + startWith(true) + ); } setEmployeeToDelete(employee: Employee): void { this.employeeService.setEmployeeToDelete(employee); diff --git a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.module.ts b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.module.ts index 9bd66a1..56b4a97 100644 --- a/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.module.ts +++ b/apps/mina-sidor-fa/src/app/pages/administration/pages/employee-form/employee-form.module.ts @@ -6,6 +6,7 @@ import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input'; import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group'; import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select'; import { DigiNgPopoverModule } from '@af/digi-ng/_popover/popover'; +import { DigiNgLoaderSpinnerModule } from '@af/digi-ng/_loader/loader-spinner'; import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; @@ -18,6 +19,8 @@ import { LocalDatePipeModule } from '@msfa-shared/pipes/local-date/local-date.mo import { EmployeeDeleteModule } from '../../components/employee-delete/employee-delete.module'; import { EditEmployeeFormComponent } from './edit-employee-form/edit-employee-form.component'; import { EmployeeFormComponent } from './employee-form.component'; +import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base'; +import { ErrorListModule } from '@msfa-shared/components/error-list/error-list.module'; @NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -34,11 +37,14 @@ import { EmployeeFormComponent } from './employee-form.component'; DigiNgPopoverModule, DigiNgFormCheckboxModule, DigiNgButtonModule, + DigiNgLoaderSpinnerModule, + DigiNgSkeletonBaseModule, LayoutModule, EmployeeDeleteModule, DigiNgDialogModule, HideTextModule, TreeNodesSelectorModule, + ErrorListModule, RolesDialogModule, ], }) diff --git a/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.html b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.html new file mode 100644 index 0000000..bd54c34 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.html @@ -0,0 +1,34 @@ +
+ + + +
+ diff --git a/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.scss b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.scss new file mode 100644 index 0000000..cda3f9a --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.scss @@ -0,0 +1,15 @@ +@import 'mixins/list'; +@import 'variables/gutters'; + +.error-list { + display: block; + margin: 1.5rem 0; + + &__validation-error-links { + @include msfa__reset-list; + display: flex; + flex-direction: column; + gap: $digi--layout--gutter; + margin: 1.5rem 0; + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.spec.ts b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.spec.ts new file mode 100644 index 0000000..4ff825a --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ErrorListComponent } from './error-list.component'; + +describe('ErrorListComponent', () => { + let component: ErrorListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ErrorListComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ErrorListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.ts b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.ts new file mode 100644 index 0000000..727fe65 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.component.ts @@ -0,0 +1,19 @@ +import { TypographyDynamicHeadingLevel } from '@af/digi-ng/_typography/typography-dynamic-heading'; +import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; + +export interface ValidationErrorLink { + elementId: string; + text: string; +} + +@Component({ + selector: 'msfa-error-list', + templateUrl: './error-list.component.html', + styleUrls: ['./error-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ErrorListComponent { + @Input() validationErrorLinks: ValidationErrorLink[] = []; + @Input() headingText: string; + @Input() headingLevel: TypographyDynamicHeadingLevel = TypographyDynamicHeadingLevel.H3; +} diff --git a/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.module.ts b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.module.ts new file mode 100644 index 0000000..d5f0eb0 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/components/error-list/error-list.module.ts @@ -0,0 +1,13 @@ +import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal'; +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { AnchorLinkModule } from '@msfa-directives/anchor-link.module'; +import { ErrorListComponent } from './error-list.component'; + +@NgModule({ + schemas: [CUSTOM_ELEMENTS_SCHEMA], + declarations: [ErrorListComponent], + imports: [CommonModule, AnchorLinkModule, DigiNgLinkInternalModule], + exports: [ErrorListComponent], +}) +export class ErrorListModule {} diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.html b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.html index 85caf3f..4028756 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.html +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.html @@ -1,88 +1,115 @@ -
- -

- {{treeNodeModel.grandChildrenInfo}} -

-
- -
- Filtrera valbara alternativ där deras namn måste innehålla den angivna texten. -
+
+
+ + +

{{generalInfo}}

+
-
- - {{treeNodeModel.toggleAllChildrenLabel}} -
- -
    -
  • - -
  • -
-
- - -
- - {{node.label}} -
+ + +
+ +
+ Filtrera valbara alternativ där deras namn måste innehålla den angivna texten. +
+
+ +
+ {{getAriaLabelForToggleAllButton(treeNodeModel)}} +
+
    +
  • + + +
  • +
+ +
- -
+ + + + +
+ +
+
+ +
+
diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.scss b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.scss index 5decd8f..2a3ed63 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.scss +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.scss @@ -4,11 +4,24 @@ @import 'variables/gutters'; .expanded-tree-node { - display: flex; - flex-direction: column; - min-height: 100%; - max-height: 300px; - overflow: auto; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-sizing: border-box; + padding-top: $digi--layout--gutter--m; + + &--is-root-node { + position: static; + } + + &__content { + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; + } &__filter { padding: 0 0.9375rem; @@ -60,53 +73,7 @@ } } - &__node-checkbox-presentation { - font-size: var(--digi--typography--font-size--s); - font-weight: 400; - display: block; - position: relative; - padding-left: 1.875rem; - cursor: pointer; - line-height: normal; - - &__box { - position: absolute; - top: 0; - left: 0; - height: 1.25rem; - width: 1.25rem; - border-radius: 0.1875rem; - background-color: var(--digi--ui--color--background); - border: 1px solid var(--digi--ui--color--background--dark); - } - - &--toggle-all { - margin: 1.25rem $digi--layout--gutter; - } - - &--checked &__box { - background-color: var(--digi--ui--color--success); - border-color: var(--digi--ui--color--success); - - &::after { - position: absolute; - left: 0.375rem; - top: 0.0625rem; - width: 0.375rem; - height: 0.6875rem; - border: solid #fff; - border-width: 0 0.125rem 0.125rem 0; - transform: rotate(35deg); - content: ''; - } - } - - &--focus &__box { - box-shadow: var(--digi--ui--outline--focus--m); - } - } - - &__node-expansion-presentation { + &__child-node-expansion-btn { display: inline-flex; padding: 0.3125rem 0.9375rem; justify-content: space-between; @@ -122,7 +89,7 @@ position: relative; &:hover, - &--focus { + &:focus { background-color: var(--digi--ui--color--background--secondary); } @@ -131,7 +98,7 @@ color: var(--digi--ui--color--background); &:hover, - &--focus { + &:focus { background-color: lighten($digi--ui--color--primary, 10%); } } @@ -139,7 +106,6 @@ &__text { text-align: left; flex-grow: 1; - max-width: rem(450); margin-right: $digi--layout--gutter--s; display: block; overflow: hidden; @@ -163,4 +129,33 @@ max-height: 1em; } } + + &__child-node-checkbox { + display: block; + margin: 0.3125rem $digi--layout--gutter; + + &--toggle-all { + margin: 1.25rem $digi--layout--gutter; + } + + ::ng-deep { + .digi-form-checkbox__label { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + } + + &__child-node-expansion-panel { + &--active { + position: absolute; + top: 0; + left: 100%; + border-left: 1px solid var(--digi--typography--color--text--disabled); + height: 100%; + width: 100%; + } + } } diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.ts b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.ts index 6858ed5..d28e695 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/expanded-tree-node/expanded-tree-node.component.ts @@ -1,13 +1,12 @@ import { TypographyDynamicHeadingLevel } from '@af/digi-ng/_typography/typography-dynamic-heading'; import { Component, - OnInit, ChangeDetectionStrategy, Input, - OnChanges, - SimpleChanges, Output, EventEmitter, + OnChanges, + SimpleChanges, } from '@angular/core'; import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive'; import { ButtonType } from '../../../../../pages/avrop/enums/button-type.enum'; @@ -26,42 +25,44 @@ import { styleUrls: ['./expanded-tree-node.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements OnInit, OnChanges { - @Input() treeNodeModel: TreeNodeModel | null = null; +export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements OnChanges { + @Input() treeNodeModel: TreeNodeModel; @Input() headingLevel: TypographyDynamicHeadingLevel = TypographyDynamicHeadingLevel.H3; - @Input() latestParentActionKey: string; - @Output() filterTreeNodeRequested = new EventEmitter(); - @Output() clickAndFocusElementRequested = new EventEmitter(); + @Input() headingText: string; + @Input() showGeneralInfo: boolean; + @Input() generalInfo: string; + @Input() visibleChildren: Array; + @Output() filterVisibleChildrenRequested = new EventEmitter(); + @Output() selectedNodesChanged = new EventEmitter(); readonly ButtonType = ButtonType; readonly FormInputSearchVariation = FormInputSearchVariation; - visibleChildren: Array | null = null; + childNodeVisibleChildren: Array; - private readonly filterTreeNodeDebouncer: Subject = new Subject(); + private readonly filterTreeNodeDebouncer: Subject = new Subject(); constructor(private treeNodesSelectorService: TreeNodesSelectorService) { super(); super.unsubscribeOnDestroy( - this.filterTreeNodeDebouncer.pipe(debounceTime(200)).subscribe(filterTreeNodeData => { - this.filterTreeNodeRequested.emit(filterTreeNodeData); + this.filterTreeNodeDebouncer.pipe(debounceTime(200)).subscribe(text => { + this.filterVisibleChildrenRequested.emit({ text, treeNode: this.treeNodeModel }); }) ); } - ngOnInit(): void { - this.refreshComponentData(); - } - ngOnChanges(changes: SimpleChanges): void { - if (changes.latestParentActionKey || changes.treeNodeModel) { - this.refreshComponentData(); - } - } + let expandedChildNode: TreeNodeModel | null = null; - private refreshComponentData(): void { - this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(this.treeNodeModel); + if (changes.visibleChildren) { + expandedChildNode = this.visibleChildren?.find(childNode => this.isExpandedNode(this.treeNodeModel, childNode)); + + this.updateChildNodeVisibleChildren({ + text: expandedChildNode?.filterText, + treeNode: expandedChildNode, + }); + } } onFilterTextChanged(event: CustomEvent<{ target: { value: string } }>, treeNode: TreeNodeModel): void { @@ -69,10 +70,7 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O return; } - this.filterTreeNodeDebouncer.next({ - text: event.detail.target.value, - treeNode, - }); + this.filterTreeNodeDebouncer.next(event.detail.target.value); } onFocusOutsideFilter(event: Event): void { @@ -80,42 +78,68 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O event.stopPropagation(); } - nodePresentationToggleAllClicked(treeNode: TreeNodeModel): void { - if (!treeNode) { + onToggleSelected(treeNode: TreeNodeModel, event: CustomEvent<{ target: { checked: boolean } }>): void { + if (!treeNode || !event?.detail?.target) { return; } - this.clickAndFocusElementRequested.emit(`#${this.treeNodesSelectorService.getToggleAllButtonId(treeNode)}`); + treeNode.isSelected = event?.detail?.target?.checked; + + this.selectedNodesChanged.emit(this.treeNodeModel); } - nodePresentationItemClicked(treeNode: TreeNodeModel): void { - if (!treeNode) { + onToggleAllChildLeafNodes(treeNode: TreeNodeModel): void { + let allChildLeafNodesAreSelected = false; + + if (!treeNode || !treeNode.children) { return; } - this.clickAndFocusElementRequested.emit(`#${this.treeNodesSelectorService.getItemButtonId(treeNode)}`); + allChildLeafNodesAreSelected = this.allChildLeafNodesAreSelected(treeNode); + + treeNode.children = treeNode.children.map(child => { + return { ...child, isSelected: !allChildLeafNodesAreSelected }; + }); + + this.selectedNodesChanged.emit(this.treeNodeModel); } - getPresentationItemId(treeNode: TreeNodeModel): string { - return this.treeNodesSelectorService.getPresentationItemId(treeNode); + onSetExpandedChild(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): void { + if (!parentTreeNode || !treeNode) { + return; + } + + parentTreeNode.expandedChildUuid = parentTreeNode.expandedChildUuid === treeNode.uuid ? null : treeNode.uuid; + + this.updateChildNodeVisibleChildren({ text: treeNode.filterText, treeNode }); } - getPresentationToggleAllId(treeNode: TreeNodeModel): string { - return this.treeNodesSelectorService.getPresentationToggleAllId(treeNode); + updateExpandedChildPanel(treeNode: TreeNodeModel): void { + if (!this.childNodeVisibleChildren) { + return; + } + + this.childNodeVisibleChildren = this.treeNodesSelectorService.getVisibleChildren(treeNode); } - getFilterDescriptionId(treeNode: TreeNodeModel): string { - return this.treeNodesSelectorService.getFilterDescriptionId(treeNode); + updateChildNodeVisibleChildren(filterTreeNodeData: FilterTreeNodeData): void { + if (!filterTreeNodeData || !filterTreeNodeData.treeNode) { + return; + } + + filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren( + filterTreeNodeData + ); + + filterTreeNodeData.treeNode.filterText = filterTreeNodeData.text; + + this.childNodeVisibleChildren = this.treeNodesSelectorService.getVisibleChildren(filterTreeNodeData.treeNode); } getFilterButtonAriaLabelText(treeNode: TreeNodeModel): string { return this.treeNodesSelectorService.getFilterButtonAriaLabelText(treeNode); } - getTreeNodeHeadingText(treeNode: TreeNodeModel): string { - return this.treeNodesSelectorService.getTreeNodeHeadingText(treeNode); - } - hasChildLeafNodes(node: TreeNodeModel): boolean { return this.treeNodesSelectorService.hasChildLeafNodes(node); } @@ -135,4 +159,16 @@ export class ExpandedTreeNodeComponent extends UnsubscribeDirective implements O hasSelectedDescendant(treeNode: TreeNodeModel): boolean { return this.treeNodesSelectorService.hasSelectedLeafNodeDescendant(treeNode); } + + getAriaLabelTextForExpansionButton(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): string { + return this.treeNodesSelectorService.getAriaLabelTextForExpansionButton(parentTreeNode, treeNode); + } + + getAriaLabelForToggleAllButton(treeNode: TreeNodeModel): string { + return this.treeNodesSelectorService.getAriaLabelForToggleAllButton(treeNode); + } + + showGeneralInfoTemplate(treeNode: TreeNodeModel): boolean { + return this.treeNodesSelectorService.showGeneralInfoTemplate(treeNode); + } } diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.html b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.html index de0b2bd..e029298 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.html +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.html @@ -1,6 +1,6 @@ -
+

{{headingText}}

@@ -18,21 +18,13 @@

- -
- +
+
@@ -45,64 +37,6 @@ {{confirmationButtonText}}
- -
-

{{node.label}}

- -
{{getAriaLabelForToggleAllButton(node)}}
-
    -
  • - - -
  • -
-
-
- - - - - - - -
diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.scss b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.scss index 49cbb64..1691c97 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.scss +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.scss @@ -41,28 +41,15 @@ font-size: var(--digi--typography--font-size--xs); } - &__expanded-nodes { - @include msfa__reset-list; - display: flex; - flex-direction: row; + &__content { overflow: auto; max-width: 100%; } - &__expanded-node { - position: relative; - display: flex; - flex-direction: column; - flex: 0 0 50%; + &__tree { max-width: 50%; - overflow: auto; - padding-top: $digi--layout--gutter--m; - padding-bottom: $digi--layout--gutter--l; - border-left: 1px solid var(--digi--typography--color--text--disabled); - - &:first-child { - border-left-width: 0; - } + height: 300px; + position: relative; } footer { diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.ts b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.ts index 12d5ef6..0f07e3d 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector-panel/tree-nodes-selector-panel.component.ts @@ -1,14 +1,5 @@ import { ButtonSize } from '@af/digi-ng/_button/button'; -import { - ChangeDetectionStrategy, - Component, - ElementRef, - EventEmitter, - Input, - OnInit, - Output, - ViewChild, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FilterTreeNodeData, TreeNodeModel, @@ -28,13 +19,10 @@ export class TreeNodesSelectorPanelComponent implements OnInit { @Output() selectedChangesConfirmed = new EventEmitter(); @Output() closePanelRequested = new EventEmitter(); - @ViewChild('treeNodesSelectorPanel') treeNodesSelector: ElementRef; - readonly ButtonSize = ButtonSize; pendingRootNode: TreeNodeModel | null = null; - expandedTreeNodes: Array | null = null; - latestParentActionKey: string = null; + visibleChildren: Array | null = null; constructor(private treeNodesSelectorService: TreeNodesSelectorService) {} @@ -44,123 +32,18 @@ export class TreeNodesSelectorPanelComponent implements OnInit { private setupPendingRootNode(): void { this.pendingRootNode = this.treeNodesSelectorService.getClonedTreeNode(this.rootNode); - this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode); - } - onToggleSelected(treeNode: TreeNodeModel, event: CustomEvent<{ target: { checked: boolean } }>): void { - if (!treeNode || !event?.detail?.target) { + if (!this.pendingRootNode) { return; } - treeNode.isSelected = event?.detail?.target?.checked; - - // eslint-disable-next-line - this.latestParentActionKey = `${treeNode.uuid}-selected-${treeNode.isSelected}`; - } - - setExpandedChild(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): void { - if (!parentTreeNode || !treeNode) { - return; - } - - parentTreeNode.expandedChildUuid = parentTreeNode.expandedChildUuid === treeNode.uuid ? null : treeNode.uuid; - - this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode); - - this.latestParentActionKey = `${parentTreeNode.uuid}-expanded-child-${parentTreeNode.expandedChildUuid}`; - } - - setFocusOnToggleAll(treeNode: TreeNodeModel, hasFocus: boolean): void { - let presentationToggleAllElement: HTMLElement = null; - let expansionColumnElement: HTMLElement = null; - - if (!treeNode) { - return; - } - - treeNode.toggleAllHasFocus = hasFocus; - - if (!hasFocus) { - return; - } - - presentationToggleAllElement = this.treeNodesSelectorService.getPresentationToggleAllElement( - this.treeNodesSelector, - treeNode - ); - - expansionColumnElement = this.treeNodesSelectorService.getExpansionColumnElement(presentationToggleAllElement); - - this.scrollElementInContainer(expansionColumnElement); - } - - private scrollElementInContainer(element: HTMLElement): void { - let elementRect: DOMRect = null; - let parentElementRect: DOMRect = null; - - if (!element || !element.parentElement) { - return; - } - - if (this.treeNodesSelectorService.isFullyVisibleElement(element, element.parentElement)) { - return; - } - - elementRect = element.getBoundingClientRect(); - - parentElementRect = element.parentElement.getBoundingClientRect(); - - element.parentElement.scrollTop = elementRect.top + element.parentElement.scrollTop - parentElementRect.top; - element.parentElement.scrollLeft = elementRect.left + element.parentElement.scrollLeft - parentElementRect.left; - } - - setFocusOnNodeItem(treeNode: TreeNodeModel, hasFocus: boolean): void { - let presentationItemElement: HTMLElement = null; - let expansionColumnElement: HTMLElement = null; - - if (!treeNode) { - return; - } - - treeNode.hasFocus = hasFocus; - - if (!hasFocus) { - return; - } - - this.latestParentActionKey = `${treeNode.uuid}-focused-item`; - - presentationItemElement = this.treeNodesSelectorService.getPresentationItemElement( - this.treeNodesSelector, - treeNode - ); - - expansionColumnElement = this.treeNodesSelectorService.getExpansionColumnElement(presentationItemElement); - - this.scrollElementInContainer(presentationItemElement); - this.scrollElementInContainer(expansionColumnElement); - } - - toggleAllChildLeafNodes(treeNode: TreeNodeModel): void { - let allChildLeafNodesAreSelected = false; - - if (!treeNode || !treeNode.children) { - return; - } - - allChildLeafNodesAreSelected = this.allChildLeafNodesAreSelected(treeNode); - - treeNode.children = treeNode.children.map(child => { - return { ...child, isSelected: !allChildLeafNodesAreSelected }; - }); - - this.latestParentActionKey = `${treeNode.uuid}-selected-${allChildLeafNodesAreSelected.toString()}`; + this.pendingRootNode.isRoot = true; + this.updateExpandedChildPanel(this.pendingRootNode); } clearSelections(): void { this.pendingRootNode = this.treeNodesSelectorService.getTreeNodeWithNoNodesSelected(this.pendingRootNode); - this.latestParentActionKey = `cleared-selections`; - this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode); + this.updateExpandedChildPanel(this.pendingRootNode); } closePanel(): void { @@ -168,65 +51,21 @@ export class TreeNodesSelectorPanelComponent implements OnInit { this.closePanelRequested.emit(); } - filterTreeNode(filterTreeNodeData: FilterTreeNodeData): void { - if (!filterTreeNodeData || !filterTreeNodeData.treeNode) { - return; - } - - filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren( - filterTreeNodeData - ); - - this.latestParentActionKey = `${filterTreeNodeData.treeNode.uuid}-filtered-${filterTreeNodeData.text}`; - this.expandedTreeNodes = this.treeNodesSelectorService.getExpandedTreeNodes(this.pendingRootNode); - } - - clickAndFocusElementByQuerySelector(selector: string): void { - const selectedItem = this.treeNodesSelector - ? this.treeNodesSelectorService.getElementByQuerySelector(this.treeNodesSelector, selector) - : null; - - if (!selectedItem) { - return; - } - - selectedItem.focus(); - selectedItem.click(); - } - - getVisibleChildren(treeNode: TreeNodeModel): Array | null { - return this.treeNodesSelectorService.getVisibleChildren(treeNode); - } - - hasChildLeafNodes(node: TreeNodeModel): boolean { - return this.treeNodesSelectorService.hasChildLeafNodes(node); - } - - allChildLeafNodesAreSelected(node: TreeNodeModel): boolean { - return this.treeNodesSelectorService.allChildLeafNodesAreSelected(node); - } - hasSelectedDescendant(treeNode: TreeNodeModel): boolean { return this.treeNodesSelectorService.hasSelectedLeafNodeDescendant(treeNode); } - getAriaLabelTextForExpansionButton(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): string { - return this.treeNodesSelectorService.getAriaLabelTextForExpansionButton(parentTreeNode, treeNode); + updateExpandedChildPanel(treeNode: TreeNodeModel): void { + this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(treeNode); } - getAriaLabelForToggleAllButton(treeNode: TreeNodeModel): string { - return this.treeNodesSelectorService.getAriaLabelForToggleAllButton(treeNode); - } + updateVisibleChildren(filterTreeNodeData: FilterTreeNodeData): void { + filterTreeNodeData.treeNode.children = this.treeNodesSelectorService.getFilteredTreeNodeChildren( + filterTreeNodeData + ); - isExpandedNode(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): boolean { - return this.treeNodesSelectorService.isExpandedNode(parentTreeNode, treeNode); - } + filterTreeNodeData.treeNode.filterText = filterTreeNodeData.text; - getToggleAllButtonId(treeNode: TreeNodeModel): string { - return this.treeNodesSelectorService.getToggleAllButtonId(treeNode); - } - - getItemButtonId(treeNode: TreeNodeModel): string { - return this.treeNodesSelectorService.getItemButtonId(treeNode); + this.visibleChildren = this.treeNodesSelectorService.getVisibleChildren(filterTreeNodeData.treeNode); } } diff --git a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector/tree-nodes-selector.component.html b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector/tree-nodes-selector.component.html index 2d2cce3..0b3cdbe 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector/tree-nodes-selector.component.html +++ b/apps/mina-sidor-fa/src/app/shared/components/tree-nodes-selector/components/tree-nodes-selector/tree-nodes-selector.component.html @@ -3,6 +3,7 @@