feature(Teknisk skuld): Refaktorisera overlay och multiselect (används i filtren i Avrop). Förberedande arbete inför att göra egna komponenter för dialog och popover (TV-845)
Merge in TEA/mina-sidor-fa-web from feature/TV-845-move-overlays-(dialog,-popover)-to-@ui to develop
Squashed commit of the following:
commit b98faf62aa47a155acb5609cdbdbc31a2c726ee9
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Tue Nov 30 09:13:48 2021 +0100
Update overlay-trigger-for.directive.ts
commit 3945b98f702e82e100cb223da8da0be02fd48d17
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Mon Nov 29 11:45:33 2021 +0100
Update avrop.service.ts
commit 4c65724cac9825a599feb003c725ac37a40d03fa
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Mon Nov 29 11:40:08 2021 +0100
cleanup
commit aad6c2fc64e7d8c4d19da5f3c061f92a953be59e
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Mon Nov 29 11:06:21 2021 +0100
refactor overlay and multiselect
commit cf57126f36416f6e9de60d0048e2494bc4141188
Merge: 94257008 609698eb
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Mon Nov 29 08:41:55 2021 +0100
Merge branch 'develop' into feature/TV-845-move-overlays-(dialog,-popover)-to-@ui
commit 942570081f4bad6e6dc68f5416619cd64d9d6bb4
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date: Wed Nov 24 15:44:56 2021 +0100
migrate Dropdown to UI and extract Overlay part for other usage
This commit is contained in:
@@ -1,34 +1,34 @@
|
|||||||
<div class="avrop-filters" *ngIf="(filtersAreLoading$ | async) === false; else loadingRef">
|
<div class="avrop-filters" *ngIf="(filtersAreLoading$ | async) === false; else loadingRef">
|
||||||
<div class="avrop-filters__filter-wrapper" *ngIf="availableTjanster$ | async as availableTjanster">
|
<div class="avrop-filters__filter-wrapper" *ngIf="availableTjanster$ | async as availableTjanster">
|
||||||
<msfa-multiselect
|
<ui-multiselect
|
||||||
[multiselectTitle]="'Tjänster'"
|
[multiselectTitle]="'Tjänster'"
|
||||||
[availableOptions]="availableTjanster"
|
[availableOptions]="availableTjanster"
|
||||||
confirmButtonText="Uppdatera filter"
|
confirmButtonText="Uppdatera filter"
|
||||||
[selectedOptions]="filteredTjanster$ | async"
|
[selectedOptions]="filteredTjanster$ | async"
|
||||||
(selectedOptionsChange)="updateSelectedTjanster($event)"
|
(selectedOptionsChange)="updateSelectedTjanster($event)"
|
||||||
></msfa-multiselect>
|
></ui-multiselect>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="avrop-filters__filter-wrapper"
|
class="avrop-filters__filter-wrapper"
|
||||||
*ngIf="availableUtforandeVerksamheter$ | async as availableUtforandeVerksamheter"
|
*ngIf="availableUtforandeVerksamheter$ | async as availableUtforandeVerksamheter"
|
||||||
>
|
>
|
||||||
<msfa-multiselect
|
<ui-multiselect
|
||||||
[multiselectTitle]="'Utförande verksamheter'"
|
[multiselectTitle]="'Utförande verksamheter'"
|
||||||
confirmButtonText="Uppdatera filter"
|
confirmButtonText="Uppdatera filter"
|
||||||
[availableOptions]="availableUtforandeVerksamheter"
|
[availableOptions]="availableUtforandeVerksamheter"
|
||||||
[selectedOptions]="filteredUtforandeVerksamheter$ | async"
|
[selectedOptions]="filteredUtforandeVerksamheter$ | async"
|
||||||
(selectedOptionsChange)="updateSelectedUtforandeVerksamheter($event)"
|
(selectedOptionsChange)="updateSelectedUtforandeVerksamheter($event)"
|
||||||
></msfa-multiselect>
|
></ui-multiselect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="avrop-filters__filter-wrapper" *ngIf="availableKommuner$ | async as availableKommuner">
|
<div class="avrop-filters__filter-wrapper" *ngIf="availableKommuner$ | async as availableKommuner">
|
||||||
<msfa-multiselect
|
<ui-multiselect
|
||||||
[multiselectTitle]="'Kommuner'"
|
[multiselectTitle]="'Kommuner'"
|
||||||
[availableOptions]="availableKommuner"
|
[availableOptions]="availableKommuner"
|
||||||
[selectedOptions]="filteredKommuner$ | async"
|
[selectedOptions]="filteredKommuner$ | async"
|
||||||
confirmButtonText="Uppdatera filter"
|
confirmButtonText="Uppdatera filter"
|
||||||
(selectedOptionsChange)="updateSelectedKommuner($event)"
|
(selectedOptionsChange)="updateSelectedKommuner($event)"
|
||||||
></msfa-multiselect>
|
></ui-multiselect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { AvropService } from '@msfa-services/avrop.service';
|
import { AvropService } from '@msfa-services/avrop.service';
|
||||||
import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option';
|
|
||||||
import { combineLatest, Observable } from 'rxjs';
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
import { MultiselectFilterOption } from '@ui/multiselect/multiselect-filter-option';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'msfa-avrop-filters',
|
selector: 'msfa-avrop-filters',
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MultiselectModule } from '@msfa-shared/components/multiselect/multiselect.module';
|
|
||||||
import { UiLoaderModule } from '@ui/loader/loader.module';
|
import { UiLoaderModule } from '@ui/loader/loader.module';
|
||||||
import { AvropFiltersComponent } from './avrop-filters.component';
|
import { AvropFiltersComponent } from './avrop-filters.component';
|
||||||
|
import { UiMultiselectModule } from '@ui/multiselect/multiselect.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
declarations: [AvropFiltersComponent],
|
declarations: [AvropFiltersComponent],
|
||||||
imports: [CommonModule, MultiselectModule, UiLoaderModule],
|
imports: [CommonModule, UiMultiselectModule, UiLoaderModule],
|
||||||
exports: [AvropFiltersComponent],
|
exports: [AvropFiltersComponent],
|
||||||
})
|
})
|
||||||
export class AvropFiltersModule {}
|
export class AvropFiltersModule {}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { DropdownTriggerForDirective } from './dropdown-trigger-for.directive';
|
|
||||||
import { DropdownComponent } from './dropdown.component';
|
|
||||||
import { OverlayModule } from '@angular/cdk/overlay';
|
|
||||||
import { A11yModule } from '@angular/cdk/a11y';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [DropdownTriggerForDirective, DropdownComponent],
|
|
||||||
imports: [CommonModule, OverlayModule, A11yModule],
|
|
||||||
exports: [DropdownTriggerForDirective, DropdownComponent],
|
|
||||||
})
|
|
||||||
export class DropdownModule {}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SortOrder } from '@msfa-enums/sort-order.enum';
|
import { SortOrder } from '@msfa-enums/sort-order.enum';
|
||||||
import { DeltagareCompact } from '@msfa-models/deltagare.model';
|
import { DeltagareCompact } from '@msfa-models/deltagare.model';
|
||||||
import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option';
|
|
||||||
import { EmployeeCompactResponse } from './employee.response.model';
|
import { EmployeeCompactResponse } from './employee.response.model';
|
||||||
|
import { MultiselectFilterOption } from '@ui/multiselect/multiselect-filter-option';
|
||||||
|
|
||||||
export interface Params {
|
export interface Params {
|
||||||
[param: string]: string | string[];
|
[param: string]: string | string[];
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { AvropParams, Params } from '@msfa-models/api/params.model';
|
|||||||
import { Avrop, AvropAndMeta } from '@msfa-models/avrop.model';
|
import { Avrop, AvropAndMeta } from '@msfa-models/avrop.model';
|
||||||
import { Handledare } from '@msfa-models/handledare.model';
|
import { Handledare } from '@msfa-models/handledare.model';
|
||||||
import { AvropApiService } from '@msfa-services/api/avrop-api.service';
|
import { AvropApiService } from '@msfa-services/api/avrop-api.service';
|
||||||
import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option';
|
|
||||||
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||||
import { HandledareApiService } from './api/handledare.api.service';
|
import { HandledareApiService } from './api/handledare.api.service';
|
||||||
|
import { MultiselectFilterOption } from '@ui/multiselect/multiselect-filter-option';
|
||||||
|
|
||||||
type Step = 1 | 2 | 3 | 4;
|
type Step = 1 | 2 | 3 | 4;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import 'mixins/form';
|
@import 'libs/styles/src/mixins/form';
|
||||||
|
|
||||||
@import 'mixins/a11y';
|
@import 'libs/styles/src/mixins/a11y';
|
||||||
.multiselect-panel {
|
.multiselect-panel {
|
||||||
&__checkboxes {
|
&__checkboxes {
|
||||||
max-width: var(--digi--typography--text--max-width);
|
max-width: var(--digi--typography--text--max-width);
|
||||||
@@ -7,18 +7,18 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
SimpleChanges,
|
SimpleChanges,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { DropdownTriggerForDirective } from '@msfa-shared/components/dropdown/dropdown-trigger-for.directive';
|
import { MultiselectFilterOption } from '@ui/multiselect/multiselect-filter-option';
|
||||||
import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { OverlayTriggerForDirective } from '@ui/overlay/overlay-trigger-for.directive';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'msfa-multiselect-panel',
|
selector: 'ui-multiselect-panel',
|
||||||
templateUrl: './multiselect-panel.component.html',
|
templateUrl: './multiselect-panel.component.html',
|
||||||
styleUrls: ['./multiselect-panel.component.scss'],
|
styleUrls: ['./multiselect-panel.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class MultiselectPanelComponent implements OnChanges {
|
export class MultiselectPanelComponent implements OnChanges {
|
||||||
@Input() dropdownRef: DropdownTriggerForDirective;
|
@Input() overlayRef: OverlayTriggerForDirective;
|
||||||
@Input() confirmButtonText = 'Spara';
|
@Input() confirmButtonText = 'Spara';
|
||||||
@Input() multiselectTitle: string;
|
@Input() multiselectTitle: string;
|
||||||
@Input() multiselectTitlePlural: string;
|
@Input() multiselectTitlePlural: string;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<button
|
<button
|
||||||
#multiselectButton
|
#multiselectButton
|
||||||
[msfaDropdownTriggerFor]="dropdown"
|
[uiOverlayTriggerFor]="overlay"
|
||||||
|
[uiOverlayPositions]="overlayPositions"
|
||||||
[attr.id]="buttonElementId"
|
[attr.id]="buttonElementId"
|
||||||
[attr.aria-controls]="panelId"
|
[attr.aria-controls]="panelId"
|
||||||
[ngClass]="{'multiselect__toggle-btn--invalid': isInvalid && showValidation}"
|
[ngClass]="{'multiselect__toggle-btn--invalid': isInvalid && showValidation}"
|
||||||
@@ -12,16 +13,18 @@
|
|||||||
<span class="msfa__a11y-sr-only"> Filtrera på {{multiselectTitle}}. </span>
|
<span class="msfa__a11y-sr-only"> Filtrera på {{multiselectTitle}}. </span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<msfa-dropdown #dropdown [attr.id]="panelId">
|
<ui-overlay #overlay>
|
||||||
<msfa-multiselect-panel
|
<div class="multiselect__overlay-content" [attr.id]="panelId" cdkTrapFocus cdkTrapFocusAutoCapture>
|
||||||
|
<ui-multiselect-panel
|
||||||
[availableOptions]="availableOptions"
|
[availableOptions]="availableOptions"
|
||||||
[selectedOptions]="selectedOptions"
|
[selectedOptions]="selectedOptions"
|
||||||
[dropdownRef]="dropdown"
|
[overlayRef]="overlay"
|
||||||
[multiselectTitle]="multiselectTitle"
|
[multiselectTitle]="multiselectTitle"
|
||||||
[confirmButtonText]="confirmButtonText"
|
[confirmButtonText]="confirmButtonText"
|
||||||
(selectedOptionsChange)="emitSelectedOptions($event)"
|
(selectedOptionsChange)="emitSelectedOptions($event)"
|
||||||
></msfa-multiselect-panel>
|
></ui-multiselect-panel>
|
||||||
</msfa-dropdown>
|
</div>
|
||||||
|
</ui-overlay>
|
||||||
|
|
||||||
<div class="msfa__a11y-sr-only" aria-live="polite">
|
<div class="msfa__a11y-sr-only" aria-live="polite">
|
||||||
<span *ngIf="selectedOptions"
|
<span *ngIf="selectedOptions"
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
@import 'variables/colors';
|
@import 'libs/styles/src/variables/colors';
|
||||||
@import 'mixins/list';
|
@import 'libs/styles/src/mixins/list';
|
||||||
@import 'variables/gutters';
|
@import 'libs/styles/src/variables/gutters';
|
||||||
|
@import 'libs/styles/src/variables/shadows';
|
||||||
|
|
||||||
.multiselect {
|
.multiselect {
|
||||||
|
&__overlay__content {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: var(--digi--ui--border--radius);
|
||||||
|
box-shadow: $msfa__shadow;
|
||||||
|
}
|
||||||
|
|
||||||
&__toggle-btn {
|
&__toggle-btn {
|
||||||
margin-right: var(--digi--layout--gutter);
|
margin-right: var(--digi--layout--gutter);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { MultiselectComponent } from './multiselect.component';
|
import { MultiselectComponent } from './multiselect.component';
|
||||||
import { DropdownModule } from '@msfa-shared/components/dropdown/dropdown.module';
|
import { DropdownModule } from '@ui/dropdown/dropdown.module';
|
||||||
|
|
||||||
describe('MultiselectComponent', () => {
|
describe('MultiselectComponent', () => {
|
||||||
let component: MultiselectComponent;
|
let component: MultiselectComponent;
|
||||||
@@ -11,9 +11,10 @@ import {
|
|||||||
ViewChild,
|
ViewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
import { DropdownTriggerForDirective } from '@msfa-shared/components/dropdown/dropdown-trigger-for.directive';
|
import { MultiselectFilterOption } from '@ui/multiselect/multiselect-filter-option';
|
||||||
import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option';
|
|
||||||
import { uuid } from '@utils/uuid.util';
|
import { uuid } from '@utils/uuid.util';
|
||||||
|
import { OverlayTriggerForDirective } from '@ui/overlay/overlay-trigger-for.directive';
|
||||||
|
import { ConnectedPosition } from '@angular/cdk/overlay';
|
||||||
|
|
||||||
interface PropagateChangeFn {
|
interface PropagateChangeFn {
|
||||||
(_: unknown): void;
|
(_: unknown): void;
|
||||||
@@ -24,7 +25,7 @@ interface PropagateTouchedFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'msfa-multiselect',
|
selector: 'ui-multiselect',
|
||||||
templateUrl: './multiselect.component.html',
|
templateUrl: './multiselect.component.html',
|
||||||
styleUrls: ['./multiselect.component.scss'],
|
styleUrls: ['./multiselect.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@@ -37,7 +38,7 @@ interface PropagateTouchedFn {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MultiselectComponent implements AfterViewInit, ControlValueAccessor {
|
export class MultiselectComponent implements AfterViewInit, ControlValueAccessor {
|
||||||
@ViewChild(DropdownTriggerForDirective) dropdownRef: DropdownTriggerForDirective;
|
@ViewChild(OverlayTriggerForDirective) dropdownRef: OverlayTriggerForDirective;
|
||||||
panelId = `panel-${uuid()}`;
|
panelId = `panel-${uuid()}`;
|
||||||
@Input() buttonElementId: string = uuid();
|
@Input() buttonElementId: string = uuid();
|
||||||
@Input() isInvalid = false;
|
@Input() isInvalid = false;
|
||||||
@@ -49,6 +50,16 @@ export class MultiselectComponent implements AfterViewInit, ControlValueAccessor
|
|||||||
@Input() selectedOptions: MultiselectFilterOption[];
|
@Input() selectedOptions: MultiselectFilterOption[];
|
||||||
@Output() selectedOptionsChange = new EventEmitter<MultiselectFilterOption[]>();
|
@Output() selectedOptionsChange = new EventEmitter<MultiselectFilterOption[]>();
|
||||||
@ViewChild('multiselectButton') multiselectButton: ElementRef;
|
@ViewChild('multiselectButton') multiselectButton: ElementRef;
|
||||||
|
|
||||||
|
overlayPositions: ConnectedPosition[] = [
|
||||||
|
{
|
||||||
|
originX: 'start',
|
||||||
|
originY: 'bottom',
|
||||||
|
overlayX: 'start',
|
||||||
|
overlayY: 'top',
|
||||||
|
offsetY: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
isDisabled = false;
|
isDisabled = false;
|
||||||
|
|
||||||
private propagateChange: PropagateChangeFn;
|
private propagateChange: PropagateChangeFn;
|
||||||
@@ -3,14 +3,14 @@ import { A11yModule } from '@angular/cdk/a11y';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { DropdownModule } from '@msfa-shared/components/dropdown/dropdown.module';
|
import { MultiselectComponent } from '@ui/multiselect/multiselect.component';
|
||||||
import { MultiselectComponent } from '@msfa-shared/components/multiselect/multiselect.component';
|
|
||||||
import { MultiselectPanelComponent } from './multiselect-panel/multiselect-panel.component';
|
import { MultiselectPanelComponent } from './multiselect-panel/multiselect-panel.component';
|
||||||
|
import { UiOverlayModule } from '@ui/overlay/overlay.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [MultiselectComponent, MultiselectPanelComponent],
|
declarations: [MultiselectComponent, MultiselectPanelComponent],
|
||||||
imports: [CommonModule, DropdownModule, A11yModule, DigiNgFormCheckboxModule, FormsModule],
|
imports: [CommonModule, A11yModule, DigiNgFormCheckboxModule, FormsModule, UiOverlayModule],
|
||||||
exports: [MultiselectComponent],
|
exports: [MultiselectComponent],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
})
|
})
|
||||||
export class MultiselectModule {}
|
export class UiMultiselectModule {}
|
||||||
@@ -1,18 +1,26 @@
|
|||||||
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
|
import { ConnectedPosition, Overlay, OverlayRef } from '@angular/cdk/overlay';
|
||||||
import { TemplatePortal } from '@angular/cdk/portal';
|
import { TemplatePortal } from '@angular/cdk/portal';
|
||||||
import { Directive, ElementRef, HostListener, Input, OnDestroy, ViewContainerRef } from '@angular/core';
|
import { Directive, ElementRef, HostListener, Input, OnDestroy, ViewContainerRef } from '@angular/core';
|
||||||
import { merge, Observable, Subscription } from 'rxjs';
|
import { merge, Observable, Subscription } from 'rxjs';
|
||||||
import { DropdownPanel } from './dropdown-panel';
|
import { OverlayPanel } from './overlay.model';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[msfaDropdownTriggerFor]',
|
selector: '[uiOverlayTriggerFor]',
|
||||||
})
|
})
|
||||||
export class DropdownTriggerForDirective implements OnDestroy {
|
export class OverlayTriggerForDirective implements OnDestroy {
|
||||||
@Input('msfaDropdownTriggerFor') public dropdownPanel: DropdownPanel;
|
@Input() public uiOverlayTriggerFor: OverlayPanel;
|
||||||
|
@Input() public uiOverlayPositions: ConnectedPosition[] = [
|
||||||
private isDropdownOpen = false;
|
{
|
||||||
|
originX: 'start',
|
||||||
|
originY: 'bottom',
|
||||||
|
overlayX: 'start',
|
||||||
|
overlayY: 'top',
|
||||||
|
offsetY: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
private isOverlayOpen = false;
|
||||||
public overlayRef: OverlayRef;
|
public overlayRef: OverlayRef;
|
||||||
private dropdownClosingActionsSub = Subscription.EMPTY;
|
private overlayClosingActionsSub = Subscription.EMPTY;
|
||||||
|
|
||||||
private onClosedCallback: () => void = () => {
|
private onClosedCallback: () => void = () => {
|
||||||
return;
|
return;
|
||||||
@@ -35,11 +43,11 @@ export class DropdownTriggerForDirective implements OnDestroy {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
toggleDropdown(): void {
|
toggleDropdown(): void {
|
||||||
this.isDropdownOpen ? this.closeDropdown() : this.openDropdown();
|
this.isOverlayOpen ? this.closeDropdown() : this.openOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
openDropdown(): void {
|
openOverlay(): void {
|
||||||
this.isDropdownOpen = true;
|
this.isOverlayOpen = true;
|
||||||
this.overlayRef = this.overlay.create({
|
this.overlayRef = this.overlay.create({
|
||||||
hasBackdrop: true,
|
hasBackdrop: true,
|
||||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||||
@@ -47,41 +55,33 @@ export class DropdownTriggerForDirective implements OnDestroy {
|
|||||||
positionStrategy: this.overlay
|
positionStrategy: this.overlay
|
||||||
.position()
|
.position()
|
||||||
.flexibleConnectedTo(this.elementRef)
|
.flexibleConnectedTo(this.elementRef)
|
||||||
.withPositions([
|
.withPositions(this.uiOverlayPositions),
|
||||||
{
|
|
||||||
originX: 'start',
|
|
||||||
originY: 'bottom',
|
|
||||||
overlayX: 'start',
|
|
||||||
overlayY: 'top',
|
|
||||||
offsetY: 3,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const templatePortal = new TemplatePortal(this.dropdownPanel.templateRef, this.viewContainerRef);
|
const templatePortal = new TemplatePortal(this.uiOverlayTriggerFor.templateRef, this.viewContainerRef);
|
||||||
|
|
||||||
this.overlayRef.attach(templatePortal);
|
this.overlayRef.attach(templatePortal);
|
||||||
this.onOpenCallback();
|
this.onOpenCallback();
|
||||||
|
|
||||||
this.dropdownClosingActionsSub = this.dropdownClosingActions().subscribe(() => this.closeDropdown());
|
this.overlayClosingActionsSub = this.dropdownClosingActions().subscribe(() => this.closeDropdown());
|
||||||
}
|
}
|
||||||
|
|
||||||
private dropdownClosingActions(): Observable<MouseEvent | void> {
|
private dropdownClosingActions(): Observable<MouseEvent | void> {
|
||||||
const backdropClick$ = this.overlayRef.backdropClick();
|
const backdropClick$ = this.overlayRef.backdropClick();
|
||||||
const detachment$ = this.overlayRef.detachments();
|
const detachment$ = this.overlayRef.detachments();
|
||||||
const dropdownClosed = this.dropdownPanel.closed;
|
const overlayClosed = this.uiOverlayTriggerFor.closed;
|
||||||
|
|
||||||
return merge(backdropClick$, detachment$, dropdownClosed);
|
return merge(backdropClick$, detachment$, overlayClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDropdown(): void {
|
closeDropdown(): void {
|
||||||
if (!this.overlayRef || !this.isDropdownOpen) {
|
if (!this.overlayRef || !this.isOverlayOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onClosedCallback();
|
this.onClosedCallback();
|
||||||
this.dropdownClosingActionsSub.unsubscribe();
|
this.overlayClosingActionsSub.unsubscribe();
|
||||||
this.isDropdownOpen = false;
|
this.isOverlayOpen = false;
|
||||||
this.overlayRef.detach();
|
this.overlayRef.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<ng-template>
|
<ng-template>
|
||||||
<div class="dropdown-content" cdkTrapFocus cdkTrapFocusAutoCapture>
|
<div class="overlay__content" cdkTrapFocus cdkTrapFocusAutoCapture>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
@import "variables/shadows";
|
@import 'libs/styles/src/variables/shadows';
|
||||||
|
|
||||||
.dropdown-content {
|
.overlay__content {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: var(--digi--ui--border--radius);
|
border-radius: var(--digi--ui--border--radius);
|
||||||
box-shadow: $msfa__shadow;
|
box-shadow: $msfa__shadow;
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { DropdownComponent } from './dropdown.component';
|
import { OverlayComponent } from './overlay.component';
|
||||||
|
|
||||||
describe('DropdownComponent', () => {
|
describe('DropdownComponent', () => {
|
||||||
let component: DropdownComponent;
|
let component: OverlayComponent;
|
||||||
let fixture: ComponentFixture<DropdownComponent>;
|
let fixture: ComponentFixture<OverlayComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ DropdownComponent ]
|
declarations: [OverlayComponent],
|
||||||
})
|
}).compileComponents();
|
||||||
.compileComponents();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(DropdownComponent);
|
fixture = TestBed.createComponent(OverlayComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Output, TemplateRef, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Output, TemplateRef, ViewChild } from '@angular/core';
|
||||||
import { DropdownPanel } from './dropdown-panel';
|
import { OverlayPanel } from './overlay.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'msfa-dropdown',
|
selector: 'ui-overlay',
|
||||||
templateUrl: './dropdown.component.html',
|
templateUrl: './overlay.component.html',
|
||||||
styleUrls: ['./dropdown.component.scss'],
|
styleUrls: ['./overlay.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class DropdownComponent implements DropdownPanel {
|
export class OverlayComponent implements OverlayPanel {
|
||||||
@ViewChild(TemplateRef) templateRef: TemplateRef<never>;
|
@ViewChild(TemplateRef) templateRef: TemplateRef<never>;
|
||||||
@Output() closed = new EventEmitter<void>();
|
@Output() closed = new EventEmitter<void>();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EventEmitter, TemplateRef } from '@angular/core';
|
import { EventEmitter, TemplateRef } from '@angular/core';
|
||||||
|
|
||||||
export interface DropdownPanel {
|
export interface OverlayPanel {
|
||||||
templateRef: TemplateRef<never>;
|
templateRef: TemplateRef<never>;
|
||||||
readonly closed: EventEmitter<void>;
|
readonly closed: EventEmitter<void>;
|
||||||
}
|
}
|
||||||
13
libs/ui/src/overlay/overlay.module.ts
Normal file
13
libs/ui/src/overlay/overlay.module.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { OverlayTriggerForDirective } from '../overlay/overlay-trigger-for.directive';
|
||||||
|
import { OverlayModule } from '@angular/cdk/overlay';
|
||||||
|
import { A11yModule } from '@angular/cdk/a11y';
|
||||||
|
import { OverlayComponent } from '@ui/overlay/overlay.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [OverlayTriggerForDirective, OverlayComponent],
|
||||||
|
imports: [CommonModule, OverlayModule, A11yModule],
|
||||||
|
exports: [OverlayTriggerForDirective, OverlayComponent],
|
||||||
|
})
|
||||||
|
export class UiOverlayModule {}
|
||||||
Reference in New Issue
Block a user