Merge pull request #83 in TEA/mina-sidor-fa-web from feature/TV-396 to develop
Squashed commit of the following: commit f5029b04d2117df86eaf6692c88bdc692059d8d6 Merge: 3d776b145f2fb5Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Sat Sep 11 06:17:07 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 3d776b10824be6b54e186104a5bcd351e5b2fb42 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 23:26:47 2021 +0200 TV-396 fixed some tests and so on commit bd57fce383ba409ae8de1869c242b5a8f51071d2 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 23:20:26 2021 +0200 TV-396 made some adjustments to the validation logic after feedback in PR commit 942cddb263d0965e772f7f34305e85737da76df4 Merge: 174dfe9ceee702Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 14:10:23 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 174dfe924f2eac979992275ddd55ed0758144efb Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 14:05:39 2021 +0200 TV-396 fixed issue with general info after restructuring.. commit 7e0d4bdf9e76e0fb58fe30358c3e729cce1f9260 Merge: da02f6c5b00453Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 12:00:29 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit da02f6cc7f4a9405ad1d8167ef18729b18973d61 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 10:43:42 2021 +0200 TV-396 added aria-expanded attribute commit 48eb24ca6e354b44ae4d4b62ce2ffa496743d0b5 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Fri Sep 10 09:46:39 2021 +0200 TV-396 moved some logic into seperate template commit 0ef787d6c3700677ae793c486930b07748365412 Merge: 6dfd7b05f81d6fAuthor: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Thu Sep 9 17:34:40 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 6dfd7b00caa45d335f3fe8619b92c282038ac5cb Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Thu Sep 9 17:33:54 2021 +0200 TV-396 used digi internal link instead of basic a-tag commit 46c17011b7f6b1f628b14ccf020c06cdc95627c8 Merge: 4338e151938b94Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Thu Sep 9 15:07:16 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 4338e153f7e4ebdf8ab65a64a6194dbd4d9fa9c7 Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Thu Sep 9 14:56:26 2021 +0200 TV-396 added error summary an validation handling for the edit form etc. commit ebb2e76993b99756d5a641ab8ca7d137be8a982f Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Wed Sep 8 22:58:44 2021 +0200 TV-396 making sure utforande verksamheter and addresses are populated as they should be when editing an employee. commit 01f4c9bf7ad8fc4ad44b0e8945182492b864d0cf Merge: 1c2aa92b06436aAuthor: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Wed Sep 8 22:07:25 2021 +0200 Merge branch 'develop-remote' into feature/TV-396 commit 1c2aa92f21aac57036ed05d5ebeab0f0e6a45c2c Author: arbetsformedlingen_garcn <christian.gardebrink@arbetsformedlingen.se> Date: Wed Sep 8 22:07:00 2021 +0200 Merge branch 'develop-remote' into bugs/TV-520 # Conflicts: # apps/mina-sidor-fa/src/app/pages/administration/pages/employee-card/employee-card.component.html
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
<div class="error-list-wrapper" [hidden]="!validationErrorLinks || validationErrorLinks.length === 0">
|
||||
<digi-notification-alert
|
||||
class="error-list"
|
||||
af-variation="danger"
|
||||
[attr.af-heading]="headingText"
|
||||
[af-heading-level]="headingLevel"
|
||||
[afCloseable]="false"
|
||||
>
|
||||
<ul class="error-list__validation-error-links">
|
||||
<li *ngFor="let validationErrorLink of validationErrorLinks;">
|
||||
<digi-ng-link-internal
|
||||
msfaAnchorLink
|
||||
class="error-list__validation-error-link"
|
||||
[afHref]="'#' + validationErrorLink.elementId"
|
||||
[afText]="validationErrorLink.text"
|
||||
></digi-ng-link-internal>
|
||||
</li>
|
||||
</ul>
|
||||
</digi-notification-alert>
|
||||
</div>
|
||||
<!-- <digi-form-error-list
|
||||
class="edit-employee-form__validation-error-summary"
|
||||
af-heading="Åtgärda följande fel för att spara dina ändringar:"
|
||||
*ngIf="validationErrorLinks && validationErrorLinks.length !== 0"
|
||||
>
|
||||
Behöver hantera ankarlänkar kopplat till den här komponenten om det ska fungera att använda den...
|
||||
<a
|
||||
[attr.href]="'#' + validationErrorLink.elementId"
|
||||
[attr.id]="first ? firstValidationErrorLinkId : 'validation-error-link-' + index"
|
||||
*ngFor="let validationErrorLink of validationErrorLinks; let first = first; let index = index"
|
||||
>
|
||||
{{ validationErrorLink.text }}
|
||||
</a>
|
||||
</digi-form-error-list> -->
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ErrorListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ErrorListComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ErrorListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -1,88 +1,115 @@
|
||||
<div class="expanded-tree-node" *ngIf="treeNodeModel">
|
||||
<digi-ng-typography-dynamic-heading
|
||||
class="expanded-tree-node__heading"
|
||||
[afText]="getTreeNodeHeadingText(treeNodeModel)"
|
||||
[afLevel]="headingLevel"
|
||||
></digi-ng-typography-dynamic-heading>
|
||||
<p class="expanded-tree-node__info" *ngIf="treeNodeModel.showGeneralInfoAboutGrandChildren">
|
||||
{{treeNodeModel.grandChildrenInfo}}
|
||||
</p>
|
||||
<div
|
||||
*ngIf="treeNodeModel.children && !treeNodeModel.showGeneralInfoAboutGrandChildren"
|
||||
class="expanded-tree-node__filter"
|
||||
>
|
||||
<digi-form-input-search
|
||||
[attr.af-variation]="FormInputSearchVariation.S"
|
||||
[attr.af-button-text]="getFilterButtonAriaLabelText(treeNodeModel)"
|
||||
[attr.af-button-type]="ButtonType.BUTTON"
|
||||
[attr.af-label]="' '"
|
||||
[attr.af-aria-labelledby]="getFilterDescriptionId(treeNodeModel)"
|
||||
(afOnFocusOutside)="onFocusOutsideFilter($event)"
|
||||
(afOnChange)="onFilterTextChanged($event, treeNodeModel)"
|
||||
(afOnKeyup)="onFilterTextChanged($event, treeNodeModel)"
|
||||
></digi-form-input-search>
|
||||
<div class="msfa__a11y-sr-only" [attr.id]="getFilterDescriptionId(treeNodeModel)">
|
||||
Filtrera valbara alternativ där deras namn måste innehålla den angivna texten.
|
||||
</div>
|
||||
<div
|
||||
class="expanded-tree-node"
|
||||
[ngClass]="{
|
||||
'expanded-tree-node--is-root-node' : treeNodeModel?.isRoot
|
||||
}"
|
||||
>
|
||||
<div class="expanded-tree-node__content">
|
||||
<digi-ng-typography-dynamic-heading
|
||||
class="expanded-tree-node__heading"
|
||||
[afText]="headingText"
|
||||
[afLevel]="headingLevel"
|
||||
></digi-ng-typography-dynamic-heading>
|
||||
<ng-container *ngIf="showGeneralInfo else treeNodeChildrenTemplate">
|
||||
<p class="expanded-tree-node__info">{{generalInfo}}</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="hasChildLeafNodes(treeNodeModel)"
|
||||
class="expanded-tree-node__node-checkbox-presentation expanded-tree-node__node-checkbox-presentation--toggle-all"
|
||||
[ngClass]="{
|
||||
'expanded-tree-node__node-checkbox-presentation--focus': treeNodeModel.toggleAllHasFocus,
|
||||
'expanded-tree-node__node-checkbox-presentation--checked': allChildLeafNodesAreSelected(treeNodeModel)
|
||||
}"
|
||||
[attr.id]="getPresentationToggleAllId(treeNodeModel)"
|
||||
(click)="nodePresentationToggleAllClicked(treeNodeModel)"
|
||||
>
|
||||
<span class="expanded-tree-node__node-checkbox-presentation__box"></span>
|
||||
<span class="expanded-tree-node__node-checkbox-presentation__text">{{treeNodeModel.toggleAllChildrenLabel}}</span>
|
||||
</div>
|
||||
<ng-container *ngIf="visibleChildren">
|
||||
<ul *ngIf="!treeNodeModel.showGeneralInfoAboutGrandChildren" class="expanded-tree-node__nodes">
|
||||
<li
|
||||
*ngFor="let childNode of visibleChildren"
|
||||
[attr.id]="getPresentationItemId(childNode)"
|
||||
[ngClass]="{'expanded-tree-node__node--leaf' : isLeafNode(childNode)}"
|
||||
class="expanded-tree-node__node"
|
||||
>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="childNode.children ? nodeExpansionPresentationTemplate : nodeCheckboxPresentationTemplate"
|
||||
[ngTemplateOutletContext]="{node: childNode, parentNode: treeNodeModel}"
|
||||
></ng-container>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #nodeCheckboxPresentationTemplate let-node="node">
|
||||
<div
|
||||
class="expanded-tree-node__node-checkbox-presentation"
|
||||
[ngClass]="{
|
||||
'expanded-tree-node__node-checkbox-presentation--focus': node.hasFocus,
|
||||
'expanded-tree-node__node-checkbox-presentation--checked': node.isSelected
|
||||
}"
|
||||
(click)="nodePresentationItemClicked(node)"
|
||||
>
|
||||
<span class="expanded-tree-node__node-checkbox-presentation__box"></span>
|
||||
<span class="expanded-tree-node__node-checkbox-presentation__text">{{node.label}}</span>
|
||||
</div>
|
||||
<ng-template #treeNodeChildrenTemplate>
|
||||
<ng-container *ngIf="treeNodeModel">
|
||||
<div *ngIf="treeNodeModel.children" class="expanded-tree-node__filter">
|
||||
<digi-form-input-search
|
||||
[attr.af-variation]="FormInputSearchVariation.S"
|
||||
[attr.af-button-text]="getFilterButtonAriaLabelText(treeNodeModel)"
|
||||
[attr.af-button-type]="ButtonType.BUTTON"
|
||||
[attr.af-label]="' '"
|
||||
[attr.af-aria-labelledby]="'filter-description-'+treeNodeModel.uuid"
|
||||
[attr.af-value]="treeNodeModel.filterText"
|
||||
(afOnFocusOutside)="onFocusOutsideFilter($event)"
|
||||
(afOnChange)="onFilterTextChanged($event, treeNodeModel)"
|
||||
(afOnKeyup)="onFilterTextChanged($event, treeNodeModel)"
|
||||
></digi-form-input-search>
|
||||
<div class="msfa__a11y-sr-only" [attr.id]="'filter-description-'+treeNodeModel.uuid">
|
||||
Filtrera valbara alternativ där deras namn måste innehålla den angivna texten.
|
||||
</div>
|
||||
</div>
|
||||
<digi-form-checkbox
|
||||
*ngIf="hasChildLeafNodes(treeNodeModel)"
|
||||
class="expanded-tree-node__child-node-checkbox expanded-tree-node__child-node-checkbox--toggle-all"
|
||||
[attr.af-label]="treeNodeModel.toggleAllChildrenLabel"
|
||||
[attr.af-aria-labelledby]="'toggle-all-description-'+treeNodeModel.uuid"
|
||||
[attr.af-checked]="allChildLeafNodesAreSelected(treeNodeModel)"
|
||||
(afOnChange)="onToggleAllChildLeafNodes(treeNodeModel)"
|
||||
></digi-form-checkbox>
|
||||
<div class="msfa__a11y-sr-only" [attr.af-id]="'toggle-all-description-'+treeNodeModel.uuid">
|
||||
{{getAriaLabelForToggleAllButton(treeNodeModel)}}
|
||||
</div>
|
||||
<ul *ngIf="visibleChildren" class="expanded-tree-node__nodes">
|
||||
<li *ngFor="let childNode of visibleChildren" class="expanded-tree-node__node">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="childNode.children ? nodeExpansionTemplate : nodeCheckboxTemplate"
|
||||
[ngTemplateOutletContext]="{node: childNode, parentNode: treeNodeModel, isExpanded: isExpandedNode(treeNodeModel, childNode)}"
|
||||
>
|
||||
</ng-container>
|
||||
</li>
|
||||
</ul>
|
||||
<ng-container
|
||||
*ngIf="showGeneralInfoTemplate(treeNodeModel)"
|
||||
[ngTemplateOutlet]="generalInfoPanelTemplate"
|
||||
></ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #nodeExpansionPresentationTemplate let-node="node" let-parentNode="parentNode">
|
||||
<div
|
||||
class="expanded-tree-node__node-expansion-presentation"
|
||||
<ng-template #nodeCheckboxTemplate let-node="node">
|
||||
<digi-form-checkbox
|
||||
class="expanded-tree-node__child-node-checkbox"
|
||||
[attr.af-id]="'item-'+node.uuid"
|
||||
[attr.af-label]="node.label"
|
||||
[attr.af-checked]="node.isSelected"
|
||||
[attr.af-value]="node.value"
|
||||
(afOnChange)="onToggleSelected(node, $event)"
|
||||
></digi-form-checkbox>
|
||||
</ng-template>
|
||||
<ng-template #nodeExpansionTemplate let-node="node" let-parentNode="parentNode" let-isExpanded="isExpanded">
|
||||
<button
|
||||
type="button"
|
||||
class="expanded-tree-node__child-node-expansion-btn"
|
||||
[ngClass]="{
|
||||
'expanded-tree-node__node-expansion-presentation--focus': node.hasFocus,
|
||||
'expanded-tree-node__node-expansion-presentation--active' : isExpandedNode(parentNode, node)
|
||||
}"
|
||||
(click)="nodePresentationItemClicked(node)"
|
||||
'expanded-tree-node__child-node-expansion-btn--active' : isExpanded
|
||||
}"
|
||||
[attr.aria-expanded]="isExpanded"
|
||||
[attr.aria-label]="getAriaLabelTextForExpansionButton(parentNode, node)"
|
||||
[attr.aria-controls]="'expansion-panel-'+node.uuid"
|
||||
(click)="onSetExpandedChild(parentNode, node)"
|
||||
>
|
||||
<span class="expanded-tree-node__node-expansion-presentation__text">{{node.label}}</span>
|
||||
<span class="expanded-tree-node__child-node-expansion-btn__text">{{node.label}}</span>
|
||||
<span
|
||||
class="expanded-tree-node__node-expansion-presentation__has-selection-dot"
|
||||
class="expanded-tree-node__child-node-expansion-btn__has-selection-dot"
|
||||
*ngIf="hasSelectedDescendant(node)"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<digi-icon-arrow-right class="expanded-tree-node__node-expansion-presentation__icon"></digi-icon-arrow-right>
|
||||
<digi-icon-arrow-right class="expanded-tree-node__child-node-expansion-btn__icon"></digi-icon-arrow-right>
|
||||
</button>
|
||||
<div
|
||||
[attr.id]="'expansion-panel-'+node.uuid"
|
||||
class="expanded-tree-node__child-node-expansion-panel"
|
||||
[ngClass]="{'expanded-tree-node__child-node-expansion-panel--active': isExpanded}"
|
||||
>
|
||||
<msfa-expanded-tree-node
|
||||
*ngIf="isExpanded"
|
||||
[treeNodeModel]="node"
|
||||
[headingText]="node.label"
|
||||
[visibleChildren]="childNodeVisibleChildren"
|
||||
(filterVisibleChildrenRequested)="updateChildNodeVisibleChildren($event)"
|
||||
(selectedNodesChanged)="updateExpandedChildPanel($event)"
|
||||
></msfa-expanded-tree-node>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #generalInfoPanelTemplate>
|
||||
<div class="expanded-tree-node__child-node-expansion-panel expanded-tree-node__child-node-expansion-panel--active">
|
||||
<msfa-expanded-tree-node
|
||||
[headingText]="treeNodeModel.grandChildrenItemType"
|
||||
[showGeneralInfo]="true"
|
||||
[generalInfo]="treeNodeModel.grandChildrenInfo"
|
||||
></msfa-expanded-tree-node>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FilterTreeNodeData>();
|
||||
@Output() clickAndFocusElementRequested = new EventEmitter<string>();
|
||||
@Input() headingText: string;
|
||||
@Input() showGeneralInfo: boolean;
|
||||
@Input() generalInfo: string;
|
||||
@Input() visibleChildren: Array<TreeNodeModel>;
|
||||
@Output() filterVisibleChildrenRequested = new EventEmitter<FilterTreeNodeData>();
|
||||
@Output() selectedNodesChanged = new EventEmitter<TreeNodeModel>();
|
||||
|
||||
readonly ButtonType = ButtonType;
|
||||
readonly FormInputSearchVariation = FormInputSearchVariation;
|
||||
|
||||
visibleChildren: Array<TreeNodeModel> | null = null;
|
||||
childNodeVisibleChildren: Array<TreeNodeModel>;
|
||||
|
||||
private readonly filterTreeNodeDebouncer: Subject<FilterTreeNodeData> = new Subject<FilterTreeNodeData>();
|
||||
private readonly filterTreeNodeDebouncer: Subject<string> = new Subject<string>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<digi-util-detect-focus-outside (afOnFocusOutside)="closePanel()">
|
||||
<digi-util-keydown-handler (afOnEsc)="closePanel()">
|
||||
<section class="tree-nodes-selector-panel" #treeNodesSelectorPanel>
|
||||
<section class="tree-nodes-selector-panel">
|
||||
<header>
|
||||
<h2 class="tree-nodes-selector-panel__heading">{{headingText}}</h2>
|
||||
<p class="msfa__a11y-sr-only">
|
||||
@@ -18,21 +18,13 @@
|
||||
</button>
|
||||
</header>
|
||||
<div class="tree-nodes-selector-panel__content" *ngIf="pendingRootNode">
|
||||
<ul class="tree-nodes-selector-panel__expanded-nodes" aria-hidden="true" *ngIf="expandedTreeNodes">
|
||||
<li class="tree-nodes-selector-panel__expanded-node" *ngFor="let node of expandedTreeNodes">
|
||||
<msfa-expanded-tree-node
|
||||
[treeNodeModel]="node"
|
||||
[latestParentActionKey]="latestParentActionKey"
|
||||
(filterTreeNodeRequested)="filterTreeNode($event)"
|
||||
(clickAndFocusElementRequested)="clickAndFocusElementByQuerySelector($event)"
|
||||
></msfa-expanded-tree-node>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="msfa__a11y-sr-only">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{node: pendingRootNode}"
|
||||
></ng-container>
|
||||
<div class="tree-nodes-selector-panel__tree">
|
||||
<msfa-expanded-tree-node
|
||||
[treeNodeModel]="pendingRootNode"
|
||||
[visibleChildren]="visibleChildren"
|
||||
(selectedNodesChanged)="updateExpandedChildPanel($event)"
|
||||
(filterVisibleChildrenRequested)="updateVisibleChildren($event)"
|
||||
></msfa-expanded-tree-node>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
@@ -45,64 +37,6 @@
|
||||
{{confirmationButtonText}}
|
||||
</digi-button>
|
||||
</footer>
|
||||
<ng-template #nodeTemplate let-node="node">
|
||||
<div class="tree-nodes-selector-panel__level">
|
||||
<h3 class="tree-nodes-selector-panel__heading">{{node.label}}</h3>
|
||||
<digi-form-checkbox
|
||||
*ngIf="hasChildLeafNodes(node)"
|
||||
[attr.af-id]="getToggleAllButtonId(node)"
|
||||
[attr.af-label]="node.selectAllChildrenLabel"
|
||||
[attr.af-aria-labelledby]="'toggle-all-description-'+node.uuid"
|
||||
[attr.af-checked]="allChildLeafNodesAreSelected(node)"
|
||||
(afOnChange)="toggleAllChildLeafNodes(node)"
|
||||
(afOnFocus)="setFocusOnToggleAll(node, true)"
|
||||
(afOnBlur)="setFocusOnToggleAll(node, false)"
|
||||
></digi-form-checkbox>
|
||||
<div [attr.af-id]="'toggle-all-description-'+node.uuid">{{getAriaLabelForToggleAllButton(node)}}</div>
|
||||
<ul *ngIf="getVisibleChildren(node) as visibleChildren" class="tree-nodes-selector-panel__nodes">
|
||||
<li
|
||||
*ngFor="let childNode of visibleChildren"
|
||||
class="tree-nodes-selector-panel__node"
|
||||
[ngClass]="{'tree-nodes-selector-panel__node--expanded': isExpandedNode(node, childNode)}"
|
||||
>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="childNode.children ? nodeExpansionTemplate : nodeCheckboxTemplate"
|
||||
[ngTemplateOutletContext]="{node: childNode, parentNode: node}"
|
||||
>
|
||||
</ng-container>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #nodeCheckboxTemplate let-node="node">
|
||||
<digi-form-checkbox
|
||||
[attr.af-id]="'item-'+node.uuid"
|
||||
[attr.af-label]="node.label"
|
||||
[attr.af-checked]="node.isSelected"
|
||||
[attr.af-value]="node.value"
|
||||
(afOnChange)="onToggleSelected(node, $event)"
|
||||
(afOnFocus)="setFocusOnNodeItem(node, true)"
|
||||
(afOnBlur)="setFocusOnNodeItem(node, false)"
|
||||
></digi-form-checkbox>
|
||||
</ng-template>
|
||||
<ng-template #nodeExpansionTemplate let-node="node" let-parentNode="parentNode">
|
||||
<button
|
||||
type="button"
|
||||
[attr.id]="getItemButtonId(node)"
|
||||
[attr.aria-label]="getAriaLabelTextForExpansionButton(parentNode, node)"
|
||||
(click)="setExpandedChild(parentNode, node)"
|
||||
(focus)="setFocusOnNodeItem(node, true)"
|
||||
(blur)="setFocusOnNodeItem(node, false)"
|
||||
>
|
||||
{{node.label}}
|
||||
</button>
|
||||
<ng-container
|
||||
*ngIf="isExpandedNode(parentNode, node)"
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{node: node}"
|
||||
>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</section>
|
||||
</digi-util-keydown-handler>
|
||||
</digi-util-detect-focus-outside>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<TreeNodeModel>();
|
||||
@Output() closePanelRequested = new EventEmitter<void>();
|
||||
|
||||
@ViewChild('treeNodesSelectorPanel') treeNodesSelector: ElementRef;
|
||||
|
||||
readonly ButtonSize = ButtonSize;
|
||||
|
||||
pendingRootNode: TreeNodeModel | null = null;
|
||||
expandedTreeNodes: Array<TreeNodeModel> | null = null;
|
||||
latestParentActionKey: string = null;
|
||||
visibleChildren: Array<TreeNodeModel> | 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<TreeNodeModel> | 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<button
|
||||
#togglePanelBtn
|
||||
(click)="togglePanel()"
|
||||
[attr.id]="buttonElementId"
|
||||
[attr.aria-controls]="panelId"
|
||||
[ngClass]="{'tree-nodes-selector__toggle-panel-btn--invalid': isInvalid && showValidation}"
|
||||
type="button"
|
||||
|
||||
@@ -36,6 +36,7 @@ interface PropagateTouchedFn {
|
||||
],
|
||||
})
|
||||
export class TreeNodesSelectorComponent implements ControlValueAccessor {
|
||||
@Input() buttonElementId: string = UUID.UUID();
|
||||
@Input() headingText: string;
|
||||
@Input() confirmationButtonText = 'Spara';
|
||||
@Input() isInvalid = false;
|
||||
|
||||
@@ -59,71 +59,6 @@ describe('TreeNodesSelectorService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExpandedTreeNodes', () => {
|
||||
it('should return a list of all tree nodes that are in an expanded chain starting with the provided tree node.', () => {
|
||||
const treeNode: TreeNodeModel = {
|
||||
label: 'root',
|
||||
isSelected: false,
|
||||
value: null,
|
||||
uuid: '1',
|
||||
expandedChildUuid: '1_1',
|
||||
children: [
|
||||
{
|
||||
label: 'level 1',
|
||||
isSelected: false,
|
||||
value: null,
|
||||
uuid: '1_1',
|
||||
expandedChildUuid: '1_1_2',
|
||||
children: [
|
||||
{
|
||||
label: 'level 2',
|
||||
isSelected: false,
|
||||
value: null,
|
||||
uuid: '1_1_1',
|
||||
},
|
||||
{
|
||||
label: 'level 2',
|
||||
isSelected: true,
|
||||
value: null,
|
||||
uuid: '1_1_2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'level 1',
|
||||
isSelected: true,
|
||||
value: null,
|
||||
uuid: '1_2',
|
||||
expandedChildUuid: '1_2_2',
|
||||
children: [
|
||||
{
|
||||
label: 'level 2',
|
||||
isSelected: false,
|
||||
value: null,
|
||||
uuid: '1_2_1',
|
||||
},
|
||||
{
|
||||
label: 'level 2',
|
||||
isSelected: true,
|
||||
value: null,
|
||||
uuid: '1_2_2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = service.getExpandedTreeNodes(treeNode);
|
||||
|
||||
expect(result).not.toBeUndefined();
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.length).toEqual(3);
|
||||
expect(result[0].uuid).toEqual('1');
|
||||
expect(result[1].uuid).toEqual('1_1');
|
||||
expect(result[2].uuid).toEqual('1_1_2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFilteredTreeNodeChildren', () => {
|
||||
it('should set the propert hideTreeNode to true for all children with a label that contains the provided text when compared case insensitive, trimming white space and return them.', () => {
|
||||
const treeNode: TreeNodeModel = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ElementRef, Injectable } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UUID } from 'angular2-uuid';
|
||||
|
||||
export interface TreeNode {
|
||||
@@ -18,8 +18,9 @@ export interface TreeNodeModel extends TreeNode {
|
||||
children?: Array<TreeNodeModel>;
|
||||
hasFocus?: boolean;
|
||||
toggleAllHasFocus?: boolean;
|
||||
showGeneralInfoAboutGrandChildren?: boolean;
|
||||
hideTreeNode?: boolean;
|
||||
isRoot?: boolean;
|
||||
filterText?: string;
|
||||
}
|
||||
|
||||
export interface FilterTreeNodeData {
|
||||
@@ -82,30 +83,7 @@ export class TreeNodesSelectorService {
|
||||
return `${isActiveNode ? 'Döljer' : 'Visar'} ${treeNode.grandChildrenItemType} för ${treeNode.label}`;
|
||||
}
|
||||
|
||||
isFullyVisibleElement(element: HTMLElement, container: HTMLElement): boolean {
|
||||
const { bottom, top, left, right } = element.getBoundingClientRect();
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
|
||||
if (top <= containerRect.top && containerRect.top - top > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bottom - containerRect.bottom > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (left <= containerRect.left && containerRect.left - left > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (right - containerRect.right > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hasChildLeafNodes(node: TreeNodeModel): boolean {
|
||||
hasChildLeafNodes(node: TreeNode): boolean {
|
||||
if (!node || !node.children || node.children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -125,42 +103,6 @@ export class TreeNodesSelectorService {
|
||||
return !node.children.some(child => this.isLeafNode(child) && !child.isSelected);
|
||||
}
|
||||
|
||||
getElementByQuerySelector(elementRef: ElementRef, selector: string): HTMLElement {
|
||||
const element = elementRef?.nativeElement as HTMLElement;
|
||||
|
||||
return element?.querySelector(selector);
|
||||
}
|
||||
|
||||
getPresentationItemElement(elementRef: ElementRef, treeNode: TreeNodeModel): HTMLElement {
|
||||
return this.getElementByQuerySelector(elementRef, `#${this.getPresentationItemId(treeNode)}`);
|
||||
}
|
||||
|
||||
getPresentationToggleAllElement(elementRef: ElementRef, treeNode: TreeNodeModel): HTMLElement {
|
||||
return this.getElementByQuerySelector(elementRef, `#${this.getPresentationToggleAllId(treeNode)}`);
|
||||
}
|
||||
|
||||
getExpansionColumnElement(element: HTMLElement): HTMLElement {
|
||||
return element?.closest('.tree-nodes-selector-panel__expanded-node');
|
||||
}
|
||||
|
||||
getExpandedTreeNodes(treeNode: TreeNodeModel): Array<TreeNodeModel> {
|
||||
let expandedChildNode: TreeNodeModel | null = null;
|
||||
|
||||
if (!treeNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
expandedChildNode = treeNode.children?.find(childNode => this.isExpandedNode(treeNode, childNode));
|
||||
|
||||
if (expandedChildNode) {
|
||||
return [treeNode].concat(this.getExpandedTreeNodes(expandedChildNode));
|
||||
}
|
||||
|
||||
return treeNode.children && treeNode.children.some(child => child.children && child.children.length > 0)
|
||||
? [treeNode, { ...treeNode, children: undefined, showGeneralInfoAboutGrandChildren: true }]
|
||||
: [treeNode];
|
||||
}
|
||||
|
||||
isExpandedNode(parentTreeNode: TreeNodeModel, treeNode: TreeNodeModel): boolean {
|
||||
if (!parentTreeNode || !treeNode) {
|
||||
return false;
|
||||
@@ -210,35 +152,7 @@ export class TreeNodesSelectorService {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `Filtrera ${treeNode.showGeneralInfoAboutGrandChildren ? treeNode.grandChildrenItemType : treeNode.label}`;
|
||||
}
|
||||
|
||||
getTreeNodeHeadingText(treeNode: TreeNodeModel): string {
|
||||
if (!treeNode) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return treeNode.showGeneralInfoAboutGrandChildren ? treeNode.grandChildrenItemType : treeNode.label;
|
||||
}
|
||||
|
||||
getToggleAllButtonId(treeNode: TreeNodeModel): string {
|
||||
return `toggle-all-${treeNode.uuid}`;
|
||||
}
|
||||
|
||||
getItemButtonId(treeNode: TreeNodeModel): string {
|
||||
return `item-${treeNode.uuid}`;
|
||||
}
|
||||
|
||||
getPresentationItemId(treeNode: TreeNodeModel): string {
|
||||
return `presentation-item-${treeNode?.uuid}`;
|
||||
}
|
||||
|
||||
getPresentationToggleAllId(treeNode: TreeNodeModel): string {
|
||||
return `presentation-toggle-all-${treeNode?.uuid}`;
|
||||
}
|
||||
|
||||
getFilterDescriptionId(treeNode: TreeNodeModel): string {
|
||||
return `filter-description-${treeNode?.uuid}`;
|
||||
return `Filtrera ${treeNode.label}`;
|
||||
}
|
||||
|
||||
getCleanedText(text: string): string {
|
||||
@@ -360,4 +274,20 @@ export class TreeNodesSelectorService {
|
||||
? treeNode.isSelected
|
||||
: !treeNode.children.some(childNode => !this.hasSelectedAllLeafNodes(childNode));
|
||||
}
|
||||
|
||||
hasExpandedChild(treeNode: TreeNode): boolean {
|
||||
if (!treeNode || !treeNode.children) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return treeNode.children.some(childNode => this.isExpandedNode(treeNode, childNode));
|
||||
}
|
||||
|
||||
showGeneralInfoTemplate(treeNode: TreeNode): boolean {
|
||||
if (!treeNode || !treeNode.grandChildrenInfo || this.hasExpandedChild(treeNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.getCleanedText(treeNode.grandChildrenInfo).length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { AnchorLinkDirective } from './anchor-link.directive';
|
||||
|
||||
describe('AnchorLinkDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
const directive = new AnchorLinkDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Directive, HostListener } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[msfaAnchorLink]',
|
||||
})
|
||||
export class AnchorLinkDirective {
|
||||
@HostListener('click', ['$event'])
|
||||
onClick(event: MouseEvent): void {
|
||||
const target = event.target as HTMLElement;
|
||||
const link = target.tagName === 'a' ? target : target.closest('a');
|
||||
const href = link?.getAttribute('href');
|
||||
const element = document.getElementById(href?.trim().replace('#', ''));
|
||||
|
||||
if (element && element.focus) {
|
||||
element.focus();
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AnchorLinkDirective } from './anchor-link.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AnchorLinkDirective],
|
||||
exports: [AnchorLinkDirective],
|
||||
})
|
||||
export class AnchorLinkModule {}
|
||||
@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
import { environment } from '@msfa-environment';
|
||||
import { Params } from '@msfa-models/api/params.model';
|
||||
import { UtforandeVerksamhetResponse } from '@msfa-models/api/utforande-verksamhet.response.model';
|
||||
import { EmployeeUtforandeVerksamhet } from '@msfa-models/employee-utforande-verksamhet.model';
|
||||
import { UtforandeAdress } from '@msfa-models/utforande-adress.model';
|
||||
import { mapResponseToUtforandeVerksamhet, UtforandeVerksamhet } from '@msfa-models/utforande-verksamhet.model';
|
||||
import {
|
||||
@@ -21,7 +22,7 @@ export class UtforandeVerksamheterService {
|
||||
constructor(private treeNodesSelectorService: TreeNodesSelectorService, private httpClient: HttpClient) {}
|
||||
|
||||
fetchUtforandeVerksamheter$(tjanstIds: number[]): Observable<UtforandeVerksamhet[]> {
|
||||
if (!tjanstIds.length) {
|
||||
if (!tjanstIds?.length) {
|
||||
return of<UtforandeVerksamhet[]>([]);
|
||||
}
|
||||
|
||||
@@ -38,10 +39,18 @@ export class UtforandeVerksamheterService {
|
||||
return selectedUtforandeVerksamheter.map(uv => uv.adresser.map(adress => adress.id)).flat();
|
||||
}
|
||||
|
||||
getTreeNodeDataFromUtforandeVerksamheter(utforandeVerksamhetList: UtforandeVerksamhet[]): TreeNode | null {
|
||||
getTreeNodeDataFromUtforandeVerksamheter(
|
||||
availableUtforandeVerksamhetList: UtforandeVerksamhet[],
|
||||
selectedUtforandeVerksamhetList: EmployeeUtforandeVerksamhet[],
|
||||
selectAll = false
|
||||
): TreeNode | null {
|
||||
let treeNode: TreeNode | null = null;
|
||||
|
||||
if (!utforandeVerksamhetList || utforandeVerksamhetList.length === 0 || !Array.isArray(utforandeVerksamhetList)) {
|
||||
if (
|
||||
!availableUtforandeVerksamhetList ||
|
||||
availableUtforandeVerksamhetList.length === 0 ||
|
||||
!Array.isArray(availableUtforandeVerksamhetList)
|
||||
) {
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
@@ -52,17 +61,24 @@ export class UtforandeVerksamheterService {
|
||||
isSelected: false,
|
||||
value: null,
|
||||
childItemType: 'Utförande verksamheter',
|
||||
children: utforandeVerksamhetList.map(
|
||||
children: availableUtforandeVerksamhetList.map(
|
||||
(utforandeVerksamhet: UtforandeVerksamhet): TreeNode => {
|
||||
const utforandeVerksahmetTreeNode: TreeNode = {
|
||||
label: utforandeVerksamhet.name,
|
||||
toggleAllChildrenLabel: 'Välj alla adresser',
|
||||
isSelected: false,
|
||||
isSelected:
|
||||
selectAll || this.isSelectedUtforandeVerksamhet(utforandeVerksamhet, selectedUtforandeVerksamhetList),
|
||||
value: utforandeVerksamhet,
|
||||
childItemType: 'Adresser',
|
||||
children: utforandeVerksamhet.adresser
|
||||
? utforandeVerksamhet.adresser.map(adress => {
|
||||
return { label: adress.name, isSelected: false, value: adress };
|
||||
return {
|
||||
label: adress.name,
|
||||
isSelected:
|
||||
selectAll ||
|
||||
this.isSelectedUtforandeAdress(utforandeVerksamhet.id, adress, selectedUtforandeVerksamhetList),
|
||||
value: adress,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
};
|
||||
@@ -75,6 +91,52 @@ export class UtforandeVerksamheterService {
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
isSelectedUtforandeVerksamhet(
|
||||
utforandeVerksamhet: UtforandeVerksamhet,
|
||||
selectedUtforandeVerksamhetList: EmployeeUtforandeVerksamhet[]
|
||||
): boolean {
|
||||
if (
|
||||
!utforandeVerksamhet ||
|
||||
!selectedUtforandeVerksamhetList ||
|
||||
selectedUtforandeVerksamhetList.length === 0 ||
|
||||
!Array.isArray(selectedUtforandeVerksamhetList)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selectedUtforandeVerksamhetList.some(
|
||||
selectedUtforandeVerksamhet => selectedUtforandeVerksamhet.id === utforandeVerksamhet.id
|
||||
);
|
||||
}
|
||||
|
||||
isSelectedUtforandeAdress(
|
||||
utforandeVerksamhetId: number,
|
||||
utforandeAdress: UtforandeAdress,
|
||||
selectedUtforandeVerksamhetList: EmployeeUtforandeVerksamhet[]
|
||||
): boolean {
|
||||
let selectedUtforandeVerksamhet: EmployeeUtforandeVerksamhet | null = null;
|
||||
|
||||
if (
|
||||
!utforandeAdress ||
|
||||
!selectedUtforandeVerksamhetList ||
|
||||
selectedUtforandeVerksamhetList.length === 0 ||
|
||||
!Array.isArray(selectedUtforandeVerksamhetList)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
selectedUtforandeVerksamhet = selectedUtforandeVerksamhetList.find(
|
||||
selectedUtforandeVerksamhet => selectedUtforandeVerksamhet.id === utforandeVerksamhetId
|
||||
);
|
||||
|
||||
return selectedUtforandeVerksamhet
|
||||
? selectedUtforandeVerksamhet.allaAdresser ||
|
||||
selectedUtforandeVerksamhet.adresser.some(
|
||||
selectedUtforandeAdress => selectedUtforandeAdress.id === utforandeAdress.id
|
||||
)
|
||||
: false;
|
||||
}
|
||||
|
||||
getSelectedUtforandeVerksamheterFromTreeNode(treeNode: TreeNode): UtforandeVerksamhet[] {
|
||||
let utforandeVerksamhetList: UtforandeVerksamhet[] = [];
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FormGroup, ValidatorFn } from '@angular/forms';
|
||||
import { TreeNode } from '@msfa-shared/components/tree-nodes-selector/services/tree-nodes-selector.service';
|
||||
|
||||
export class EmployeeValidator {
|
||||
static HasSelectedAtLeastOneRole(roleFormControlNames: Array<string>): ValidatorFn {
|
||||
@@ -12,4 +13,20 @@ export class EmployeeValidator {
|
||||
: { noRoleSelected: true };
|
||||
};
|
||||
}
|
||||
|
||||
static HasSelectedAtLeastOneUtforandeVerksamhet(
|
||||
utforandeVerksamheterFormControlName: string,
|
||||
selectAllUtforandeVerksamheterFormControlName: string,
|
||||
validationFn: (treeNode: TreeNode | null | undefined) => boolean
|
||||
): ValidatorFn {
|
||||
return (fg: FormGroup): { [key: string]: unknown } => {
|
||||
if (fg?.get(selectAllUtforandeVerksamheterFormControlName)?.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return validationFn(fg?.get(utforandeVerksamheterFormControlName)?.value)
|
||||
? null
|
||||
: { noUtforandeVerksamhetSelected: true };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@ import { TreeNode } from '@msfa-shared/components/tree-nodes-selector/services/t
|
||||
export class TreeNodeValidator {
|
||||
static IsValidTreeNode(
|
||||
validationFn: (treeNode: TreeNode | null | undefined) => boolean,
|
||||
nameOfError: string,
|
||||
allUtforandeVerksamheterFormControl: AbstractControl
|
||||
nameOfError: string
|
||||
): ValidatorFn {
|
||||
return (control: AbstractControl): { [key: string]: unknown } => {
|
||||
const isValid = validationFn(control.value);
|
||||
|
||||
const validationObj = {};
|
||||
|
||||
if (isValid || allUtforandeVerksamheterFormControl?.value) {
|
||||
if (isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user