@funidata/ngx-fudis
Version:
Funidata Design System.
11,569 lines • 897 kB
JavaScript
import * as i3 from '@angular/common';
import { CommonModule, AsyncPipe, KeyValuePipe, NgTemplateOutlet } from '@angular/common';
import * as i3$1 from '@angular/forms';
import { Validators, FormGroup, FormControl, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import * as i1$1 from '@angular/cdk/layout';
import { LayoutModule } from '@angular/cdk/layout';
import { MatButtonModule } from '@angular/material/button';
import * as i4 from '@angular/material/datepicker';
import { MatDatepickerInput, MatDatepickerToggle, MatDatepickerToggleIcon, MatDatepicker, MatDatepickerModule } from '@angular/material/datepicker';
import * as i1 from '@angular/material/dialog';
import { MatDialogTitle, MatDialogRef, MatDialogContent, MatDialogActions, MatDialogClose, MatDialogModule } from '@angular/material/dialog';
import { MatSuffix, MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import * as i2$1 from '@angular/material/core';
import { MAT_NATIVE_DATE_FORMATS, NativeDateAdapter, MAT_DATE_LOCALE, DateAdapter, MAT_DATE_FORMATS, MatNativeDateModule } from '@angular/material/core';
import * as i0 from '@angular/core';
import { Input, HostBinding, ChangeDetectionStrategy, Component, Injectable, signal, ViewEncapsulation, Host, Optional, DOCUMENT, Inject, EventEmitter, Output, effect, Directive, ViewChild, inject, DestroyRef, ChangeDetectorRef, Injector, computed, HostListener, ViewContainerRef, ContentChild, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { BehaviorSubject, fromEvent, Subject, debounceTime, switchMap, EMPTY, startWith, Subscription, auditTime } from 'rxjs';
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
import { ComponentPortal } from '@angular/cdk/portal';
import * as i2 from '@angular/cdk/overlay';
/**
* Displays a decorative or semantic icon.
*
* Use this component to enhance recognition, meaning, or visual hierarchy.
*/
class IconComponent {
constructor(elementRef) {
this.elementRef = elementRef;
/**
* Binding host CSS class to component wrapper
*/
this._classes = 'fudis-icon-host';
/**
* Icon color: 'yellow' | 'red' | 'gray-dark' | 'gray-light' | 'primary' | 'primary-dark' |
* 'green' | 'white'
*/
this.color = 'gray-dark';
/**
* Icon rotation: 'flip-180' | 'cw-90' | 'ccw-90' | 'none'
*/
this.rotate = 'none';
/**
* CSS class list
*/
this._classList = new BehaviorSubject([]);
elementRef.nativeElement.style.cssText = `
display: inline-flex;
vertical-align: middle;
`;
}
ngOnChanges(changes) {
if (changes.icon?.currentValue !== changes.icon?.previousValue ||
changes.color?.currentValue !== changes.color?.previousValue ||
changes.rotate?.currentValue !== changes.rotate?.previousValue) {
this._classList.next(this._getClasses());
}
}
/**
* Get CSS classes with correct color, rotate and size suffixes
*/
_getClasses() {
this._iconSize = this.icon.includes('-small') ? 'sm' : 'lg';
const cssClasses = [
'fudis-icon',
`fudis-icon__color__${this.color}`,
`fudis-icon__rotate__${this.rotate}`,
`fudis-icon__${this._iconSize}`,
`fudis-icon__${this.icon}`,
];
return cssClasses;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: IconComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: IconComponent, isStandalone: true, selector: "fudis-icon", inputs: { icon: "icon", color: "color", rotate: "rotate" }, host: { properties: { "class": "this._classes" } }, usesOnChanges: true, ngImport: i0, template: "<ng-container>\n @if (icon === \"achievement\") {\n <svg\n id=\"achievement\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M24.373 12.384c-.277.894-.664 1.75-1.152 2.55a7.743 7.743 0 0 1-1.067-2.251 29.695 29.695 0 0 1 1.152-3.616 21.15 21.15 0 0 1 1.067 3.317ZM22.209 15.67a8.063 8.063 0 0 1-1.067 2.186 6.729 6.729 0 0 1-1.067-2.133 8.66 8.66 0 0 1 1.067-2.422c.49.722.85 1.523 1.067 2.368Z\"\n />\n <path\n d=\"M24.064 17.888a10.932 10.932 0 0 1-2.9-.47 9.271 9.271 0 0 1 2.132-1.813c.984.023 1.96.177 2.902.459a9.526 9.526 0 0 1-2.134 1.824ZM22.73 20.267a9.804 9.804 0 0 1-2.634 1.066 9.826 9.826 0 0 1-2.496-1.365 9.802 9.802 0 0 1 2.634-1.067c.892.336 1.732.796 2.496 1.366ZM18.826 15.403c.347.778.549 1.613.598 2.464-.181.576-2.027 2.133-2.027 2.133a7.06 7.06 0 0 1-.597-2.475c.192-.608 2.026-2.122 2.026-2.122Z\"\n />\n <path\n d=\"m18.112 19.339 3.307-1.664-2.496 2.282-4.587 2.592.416-1.216 3.36-1.994ZM7.66 12.576c.255.84.613 1.647 1.066 2.4.47-.648.83-1.369 1.067-2.133a29.196 29.196 0 0 0-1.067-3.467 21.546 21.546 0 0 0-1.067 3.2ZM9.792 15.67a8.064 8.064 0 0 0 1.067 2.186 6.73 6.73 0 0 0 1.066-2.133A8.663 8.663 0 0 0 10.86 13.3a7.56 7.56 0 0 0-1.067 2.368Z\"\n />\n <path\n d=\"M7.936 17.888c.984-.025 1.96-.183 2.901-.47a9.27 9.27 0 0 0-2.133-1.813c-.983.023-1.959.177-2.901.459a9.525 9.525 0 0 0 2.133 1.824ZM11.904 21.333a9.825 9.825 0 0 0 2.496-1.365 9.802 9.802 0 0 0-2.635-1.067c-.891.336-1.732.796-2.495 1.366.821.481 1.709.84 2.634 1.066ZM13.174 15.403a7.062 7.062 0 0 0-.598 2.464c.181.576 2.027 2.133 2.027 2.133.347-.782.55-1.62.597-2.475-.192-.608-2.026-2.122-2.026-2.122Z\"\n />\n <path d=\"m13.888 19.339-3.307-1.664 2.496 2.282 4.587 2.592-.406-1.216-3.37-1.994Z\" />\n </svg>\n }\n\n @if (icon === \"achievement-small\") {\n <svg\n id=\"achievement-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M14.569 5.603a8.87 8.87 0 0 1-.904 2 6.079 6.079 0 0 1-.837-1.766c.24-.964.543-1.912.904-2.837.35.843.63 1.713.837 2.603ZM12.87 8.18c-.19.61-.473 1.19-.837 1.715a5.282 5.282 0 0 1-.837-1.673c.18-.673.463-1.313.837-1.9.384.566.667 1.195.837 1.858Z\"\n />\n <path\n d=\"M14.327 9.92a8.577 8.577 0 0 1-2.276-.368 7.271 7.271 0 0 1 1.673-1.422 8.636 8.636 0 0 1 2.277.36 7.473 7.473 0 0 1-1.674 1.43ZM13.28 11.787a7.689 7.689 0 0 1-2.067.836 7.708 7.708 0 0 1-1.958-1.07 7.688 7.688 0 0 1 2.067-.838c.7.264 1.359.624 1.958 1.072ZM10.217 7.97c.271.612.43 1.266.468 1.934-.142.452-1.59 1.673-1.59 1.673a5.54 5.54 0 0 1-.468-1.941c.15-.477 1.59-1.665 1.59-1.665Z\"\n />\n <path\n d=\"m9.657 11.059 2.594-1.306-1.958 1.79-3.599 2.034.327-.954 2.636-1.564ZM1.456 5.753c.2.66.481 1.292.837 1.883a5.749 5.749 0 0 0 .837-1.674 22.903 22.903 0 0 0-.837-2.72 16.904 16.904 0 0 0-.837 2.511ZM3.13 8.18c.19.61.473 1.19.837 1.715a5.28 5.28 0 0 0 .837-1.673 6.795 6.795 0 0 0-.837-1.9A5.933 5.933 0 0 0 3.13 8.18Z\"\n />\n <path\n d=\"M1.674 9.92a8.577 8.577 0 0 0 2.276-.368A7.272 7.272 0 0 0 2.276 8.13 8.636 8.636 0 0 0 0 8.49a7.473 7.473 0 0 0 1.674 1.43ZM4.787 12.623a7.708 7.708 0 0 0 1.958-1.07 7.688 7.688 0 0 0-2.067-.838 7.97 7.97 0 0 0-1.958 1.072c.644.377 1.34.66 2.067.836ZM5.782 7.97a5.54 5.54 0 0 0-.469 1.934c.143.452 1.59 1.673 1.59 1.673a5.54 5.54 0 0 0 .47-1.941c-.152-.477-1.59-1.665-1.59-1.665Z\"\n />\n <path d=\"M6.343 11.059 3.75 9.753l1.958 1.79 3.599 2.034-.318-.954-2.645-1.564Z\" />\n </svg>\n }\n\n @if (icon === \"alert\") {\n <svg\n id=\"alert\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.634 8.5a1 1 0 0 1 1.73 0l7.5 13a1 1 0 0 1-.86 1.5h-15a1 1 0 0 1-.87-1.5l7.5-13Zm8.37 13.5-7.5-13-7.5 13h15Z\"\n clip-rule=\"evenodd\"\n />\n <circle cx=\"16.504\" cy=\"19.75\" r=\".75\" />\n <path d=\"M16.504 17.75a.75.75 0 0 1-.75-.75v-3a.75.75 0 0 1 1.5 0v3a.76.76 0 0 1-.75.75Z\" />\n </svg>\n }\n\n @if (icon === \"alert-fill\") {\n <svg\n id=\"alert-fill\"\n fill=\"none\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path fill=\"#fff\" fill-rule=\"evenodd\" d=\"m16.5 9.5 8 14h-16l8-14Z\" clip-rule=\"evenodd\" />\n <path\n fill=\"#B83C2E\"\n fill-rule=\"evenodd\"\n d=\"M15.634 9.5a1 1 0 0 1 1.73 0l7.5 13a1 1 0 0 1-.86 1.5h-15a1 1 0 0 1-.87-1.5l7.5-13Zm8.37 13.5-7.5-13-7.5 13h15Z\"\n clip-rule=\"evenodd\"\n />\n <circle fill=\"#B83C2E\" cx=\"16.504\" cy=\"20.75\" r=\".75\" />\n <path\n fill=\"#B83C2E\"\n d=\"M16.504 18.75a.75.75 0 0 1-.75-.75v-3a.75.75 0 0 1 1.5 0v3a.76.76 0 0 1-.75.75Z\"\n />\n </svg>\n }\n\n @if (icon === \"alert-small\") {\n <svg\n id=\"alert-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M7.186 1.47a.941.941 0 0 1 1.628 0l7.06 12.237a.942.942 0 0 1-.81 1.412H.945a.941.941 0 0 1-.819-1.412l7.06-12.236Zm7.879 12.708L8.005 1.941.945 14.178h14.12Z\"\n clip-rule=\"evenodd\"\n />\n <circle cx=\"8.005\" cy=\"12.06\" r=\".706\" />\n <path\n d=\"M8.005 10.178a.706.706 0 0 1-.706-.706V6.648a.706.706 0 1 1 1.412 0v2.824a.715.715 0 0 1-.706.706Z\"\n />\n </svg>\n }\n\n @if (icon === \"archive\") {\n <svg\n id=\"archive\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M25.429 11.83a.533.533 0 0 0-.533.533v10.304a1.6 1.6 0 0 1-1.6 1.6H9.78a1.6 1.6 0 0 1-1.6-1.6V12.363a.533.533 0 1 0-1.067 0v10.304a2.667 2.667 0 0 0 2.667 2.666h13.515a2.677 2.677 0 0 0 2.666-2.666V12.363a.533.533 0 0 0-.533-.534Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M8 6.4h17.066a2.667 2.667 0 0 1 2.667 2.667v1.962h-22.4V9.067A2.667 2.667 0 0 1 8 6.4ZM6.4 9.963h20.266v-.896a1.6 1.6 0 0 0-1.6-1.6H8a1.61 1.61 0 0 0-1.6 1.6v.896ZM20.267 17.248c0 .784-.636 1.419-1.42 1.419H14.23a1.419 1.419 0 0 1-1.43-1.419v-.352c0-.79.64-1.43 1.43-1.43h4.619a1.43 1.43 0 0 1 1.419 1.43v.352Zm-6.4-.352v.352a.363.363 0 0 0 .362.352h4.619a.352.352 0 0 0 .352-.352v-.352a.363.363 0 0 0-.352-.363h-4.62a.363.363 0 0 0-.362.363Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"arrow-big\") {\n <svg\n id=\"arrow-big\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.925 24.534a1.6 1.6 0 0 1-1.6-1.6v-9.93l-3.06 3.064a1.596 1.596 0 0 1-2.259.005 1.598 1.598 0 0 1-.011-2.258l5.792-5.87c.315-.322.75-.495 1.174-.478.39-.016.858.161 1.166.47l5.861 5.87a1.6 1.6 0 1 1-2.264 2.26l-3.2-3.202v10.07a1.6 1.6 0 0 1-1.6 1.6L36.267 16\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"arrow-dashed\") {\n <svg\n id=\"arrow-dashed\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M23.466 17.067H16v-2.134h7.466v2.134Zm-10.666 0H5.333v-2.134H12.8v2.134Z\" />\n <path d=\"m26.506 16-6.016 4.267v-8.534L26.506 16Z\" />\n </svg>\n }\n\n @if (icon === \"arrow-solid\") {\n <svg\n id=\"arrow-solid\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M25.868 15.182a1 1 0 0 1 0 1.636l-7.865 5.539a1 1 0 0 1-1.576-.818V10.461a1 1 0 0 1 1.576-.818l7.865 5.54Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M5.5 16a1 1 0 0 1 1-1H20a1 1 0 1 1 0 2H6.5a1 1 0 0 1-1-1Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"back\") {\n <svg\n id=\"back\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m18.684 14.403-.018-.003h-7.37l3.486-3.411c.2-.197.2-.513 0-.709a.518.518 0 0 0-.723 0l-4.25 4.16a.686.686 0 0 0 0 .986l4.25 4.16c.2.196.524.196.723 0 .2-.194.2-.512 0-.708l-3.486-3.411h7.37v.004c1.49-.037 2.667 1.19 2.667 2.662s-1.178 2.699-2.667 2.663v.004h-1.6a.533.533 0 0 0 0 1.067h1.6a3.737 3.737 0 0 0 3.733-3.734c0-2.057-1.654-3.756-3.715-3.73\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"brochure\") {\n <svg\n id=\"brochure\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M11 8.5a.5.5 0 0 0-.5.5v13.5a.5.5 0 0 0 .5.5h7v-2a1.5 1.5 0 0 1 1.5-1.5h2V9a.5.5 0 0 0-.5-.5H11Zm9.793 12H19.5a.5.5 0 0 0-.5.5v1.293l1.793-1.793ZM9.5 9A1.5 1.5 0 0 1 11 7.5h10A1.5 1.5 0 0 1 22.5 9v11.207L18.707 24H11a1.5 1.5 0 0 1-1.5-1.5V9Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M12 12a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"bullets\") {\n <svg\n id=\"bullets\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <circle cx=\"7\" cy=\"11\" r=\"1\" />\n <rect width=\"16\" height=\"2\" x=\"10\" y=\"10\" rx=\"1\" />\n <circle cx=\"7\" cy=\"16\" r=\"1\" />\n <rect width=\"16\" height=\"2\" x=\"10\" y=\"15\" rx=\"1\" />\n <circle cx=\"7\" cy=\"21\" r=\"1\" />\n <rect width=\"16\" height=\"2\" x=\"10\" y=\"20\" rx=\"1\" />\n </svg>\n }\n\n @if (icon === \"calendar\") {\n <svg\n id=\"calendar\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M22.394 24.533c.552 0 1.073-.813 1.073-1.365v-12.8c0-.553-.52-.768-1.073-.768H19.2V8.533h-1.066V9.6h-4.267V8.533H12.8V9.6H9.594c-.518 0-1.06.244-1.06.768v12.8c0 .553.507 1.365 1.06 1.365h12.8ZM9.6 10.667h3.2v1.066h1.067v-1.066h4.267v1.066H19.2v-1.066h3.2V12.8H9.6v-2.133Zm0 3.2h12.8v8.868c0 .276-.163.732-.44.732h-12c-.275 0-.36-.456-.36-.732v-8.868Zm4.267 5.333h-2.133v2.133h2.133V19.2Zm0-3.2h-2.133v2.133h2.133V16Zm3.2 3.2h-2.134v2.133h2.133V19.2Zm0-3.2h-2.134v2.133h2.133V16Zm3.2 3.2h-2.133v2.133h2.133V19.2Zm0-3.2h-2.133v2.133h2.133V16Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"change-log\") {\n <svg\n id=\"change-log\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16.5 15.717V11.2h-1v5.083l2.91 1.746.514-.858-2.424-1.454Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M9.066 16a6.934 6.934 0 1 1 3.736 6.152l.494-.95A5.862 5.862 0 1 0 10.138 16H9.066Z\"\n />\n <path d=\"M7.467 16h4.266L9.6 18.667 7.467 16Z\" />\n </svg>\n }\n\n @if (icon === \"check\") {\n <svg\n id=\"check\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M22.726 9.437a1.5 1.5 0 0 1 .371 2.088l-7.448 10.667a1.5 1.5 0 0 1-2.347.143l-4.285-4.779a1.5 1.5 0 0 1 2.234-2.003l3.022 3.37 6.364-9.115a1.5 1.5 0 0 1 2.089-.371Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"check-indeterminate-small\") {\n <svg\n id=\"check-indeterminate-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <rect width=\"12\" height=\"3\" x=\"2\" y=\"7\" rx=\"1.5\" />\n </svg>\n }\n\n @if (icon === \"check-small\") {\n <svg\n id=\"check-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13.572 3.18a1 1 0 0 1 .248 1.392l-5.713 8.182a1 1 0 0 1-1.564.095L3.256 9.184a1 1 0 1 1 1.489-1.335l2.444 2.726 4.991-7.147a1 1 0 0 1 1.393-.248Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"checkmark-circle\") {\n <svg\n id=\"checkmark-circle\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 25a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0 1c5.523 0 10-4.477 10-10S21.523 6 16 6 6 10.477 6 16s4.477 10 10 10Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M21.573 11.18a1 1 0 0 1 .247 1.393l-5.713 8.181a1 1 0 0 1-1.564.095l-3.287-3.665a1 1 0 0 1 1.489-1.335l2.444 2.726 4.991-7.147a1 1 0 0 1 1.393-.248Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"chevron\") {\n <svg\n id=\"chevron\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13.414 10.133a.61.61 0 0 0-.434 1.044L17.818 16l-4.838 4.822a.61.61 0 0 0 .434 1.045c.157 0 .315-.061.434-.18l5.102-5.085a.848.848 0 0 0 0-1.204l-5.102-5.085a.618.618 0 0 0-.434-.18\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"chevron-ring\") {\n <svg\n id=\"chevron-ring\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g fill-rule=\"evenodd\" clip-path=\"url(#a)\" clip-rule=\"evenodd\">\n <path\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n />\n <path\n d=\"M13.414 10.133a.61.61 0 0 0-.434 1.044L17.818 16l-4.838 4.822a.61.61 0 0 0 .434 1.045c.157 0 .315-.061.434-.18l5.102-5.085a.848.848 0 0 0 0-1.204l-5.102-5.085a.618.618 0 0 0-.434-.18Z\"\n />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"chevron-ring-fill\") {\n <svg\n id=\"chevron-ring-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path fill=\"#fff\" d=\"M29 16c0 7.18-5.82 13-13 13S3 23.18 3 16 8.82 3 16 3s13 5.82 13 13Z\" />\n <path\n fill=\"#484848\"\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill=\"#484848\"\n fill-rule=\"evenodd\"\n d=\"M13.414 10.133a.61.61 0 0 0-.434 1.044L17.818 16l-4.838 4.822a.61.61 0 0 0 .434 1.045c.157 0 .315-.061.434-.18l5.102-5.085a.848.848 0 0 0 0-1.204l-5.102-5.085a.618.618 0 0 0-.434-.18\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"clock\") {\n <svg\n id=\"clock\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M9.066 16a6.933 6.933 0 1 1 13.867 0 6.933 6.933 0 0 1-13.867 0Zm12.8 0A5.867 5.867 0 0 0 16 10.133 5.877 5.877 0 0 0 10.133 16a5.867 5.867 0 0 0 11.733 0Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M16.5 15.71V11.2h-1v5.09l2.908 1.659.496-.869-2.404-1.37Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M11.2 15.467h1.067v1.067H11.2zM19.733 15.467H20.8v1.067h-1.067zM15.467 19.733h1.067V20.8h-1.067z\"\n />\n </svg>\n }\n\n @if (icon === \"close\") {\n <svg\n id=\"close\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M20.255 20.932a.672.672 0 0 1-.47-.202l-8.533-8.534a.672.672 0 1 1 .95-.938l8.533 8.533a.672.672 0 0 1 0 .939.715.715 0 0 1-.48.202Z\"\n />\n <path\n d=\"M11.721 20.932a.714.714 0 0 0 .48-.202l8.533-8.534a.672.672 0 0 0-.949-.938l-8.533 8.533a.672.672 0 0 0 .469 1.141Z\"\n />\n </svg>\n }\n\n @if (icon === \"close-big\") {\n <svg\n id=\"close-big\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M25.566 6.403a.75.75 0 0 1 0 1.06L7.464 25.567a.75.75 0 0 1-1.06-1.061L24.504 6.403a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M6.403 6.403a.75.75 0 0 1 1.06 0l18.103 18.102a.75.75 0 0 1-1.06 1.06L6.402 7.465a.75.75 0 0 1 0-1.061Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"code\") {\n <svg\n id=\"code\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"m7.563 16.277 3.755 2.464c.15.107.256.203.32.299a.609.609 0 0 1 .096.341.66.66 0 0 1-.086.352.304.304 0 0 1-.256.128.524.524 0 0 1-.16-.032c-.053-.032-.138-.074-.245-.149l-4.064-2.71c-.01 0-.032-.01-.053-.031-.31-.203-.47-.427-.47-.662 0-.234.15-.458.46-.661.031-.021.052-.032.063-.043l4.064-2.666c.096-.054.17-.107.235-.128.064-.022.117-.043.17-.043.107 0 .192.043.246.128a.53.53 0 0 1 .096.33.645.645 0 0 1-.096.353.971.971 0 0 1-.32.298l-3.755 2.432ZM14.508 20.235c-.107.245-.214.416-.31.501a.484.484 0 0 1-.341.128.484.484 0 0 1-.352-.15.468.468 0 0 1-.15-.362c0-.043.011-.085.022-.139.01-.053.032-.106.053-.16l4.555-7.669c.117-.256.224-.427.32-.512a.488.488 0 0 1 .33-.139c.15 0 .267.054.374.15.107.096.15.213.15.352 0 .042-.011.085-.022.128a.537.537 0 0 1-.053.138l-4.576 7.734ZM24.875 16.277l-3.744-2.432a1.164 1.164 0 0 1-.33-.298.609.609 0 0 1-.097-.342c0-.138.032-.256.085-.33a.286.286 0 0 1 .246-.128c.053 0 .106.01.17.042.064.022.14.075.235.139l4.064 2.667c.01.01.032.021.064.042.31.192.459.416.459.662 0 .234-.15.458-.448.661a.608.608 0 0 1-.075.043l-4.064 2.709-.021.01c-.182.107-.31.171-.384.171a.286.286 0 0 1-.246-.128c-.064-.085-.085-.202-.085-.352 0-.128.032-.234.096-.341.064-.096.17-.203.33-.299l3.745-2.496Z\"\n />\n </svg>\n }\n\n @if (icon === \"decrease-indent\") {\n <svg\n id=\"decrease-indent\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M9.066 16.533h9.6v-1.067h-9.6v1.067ZM9.066 14.4h9.6v-1.067h-9.6V14.4ZM9.066 12.267h13.867V11.2H9.066v1.067ZM9.066 18.667h9.6V17.6h-9.6v1.067ZM9.066 20.8h13.867v-1.067H9.066V20.8ZM19.733 16l3.2 2.133v-4.266L19.733 16Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"delete\") {\n <svg\n id=\"delete\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M9.067 25.067v-16h13.867v16H9.067ZM10.134 24h11.733V10.133H10.134V24Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M15.467 12.267h1.067V20.8h-1.067zM12.267 14.4h1.067v6.4h-1.067zM18.667 14.4h1.067v6.4h-1.067zM6.934 9.067h18.133v1.067H6.934zM13.334 6.933v2.134h1.067V8h3.2v1.067h1.066V6.933h-5.333Z\"\n />\n </svg>\n }\n\n @if (icon === \"dot\") {\n <svg\n id=\"dot\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <circle cx=\"16\" cy=\"16\" r=\"4.267\" />\n </svg>\n }\n\n @if (icon === \"edit\") {\n <svg\n id=\"edit\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"m7.797 24.235 4.64-1.248-3.381-3.382-1.26 4.63ZM9.637 19.04l8.794-8.795 3.387 3.386-8.795 8.795-3.386-3.387ZM22.517 12.907l1.494-1.483a.768.768 0 0 0 0-1.067L21.706 8a.757.757 0 0 0-1.066 0l-1.494 1.483 3.371 3.424Z\"\n />\n </svg>\n }\n\n @if (icon === \"editor\") {\n <svg\n id=\"editor\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m11.246 16.85 4.258-9.58a.532.532 0 0 1 .713-.29c.133.058.23.165.279.29l4.258 9.58a.533.533 0 0 1-.975.433L18.498 14.4h-4.996l-1.281 2.883a.533.533 0 1 1-.975-.433Zm2.73-3.517h4.048L16 8.78l-2.023 4.553ZM13.679 24a1.004 1.004 0 1 1-2.008 0 1.004 1.004 0 0 1 2.008 0Zm5.521-1.004a1.004 1.004 0 1 0 0 2.008 1.004 1.004 0 0 0 0-2.008ZM14.934 24a1.004 1.004 0 1 1 2.008 0 1.004 1.004 0 0 1-2.008 0Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"exclamation-mark-circle\") {\n <svg\n id=\"exclamation-mark-circle\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 25a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0 1c5.523 0 10-4.477 10-10S21.523 6 16 6 6 10.477 6 16s4.477 10 10 10Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M16 18.5c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1Zm0-1.5c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1Z\"\n />\n </svg>\n }\n\n @if (icon === \"exclamation-mark-circle-fill\") {\n <svg\n id=\"exclamation-mark-circle-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <circle cx=\"16\" cy=\"15\" r=\"10\" fill=\"#B83C2E\" />\n <path\n fill=\"#fff\"\n d=\"M16 18.5c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1Zm0-1.5c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1Z\"\n />\n </svg>\n }\n\n @if (icon === \"exclamation-mark-circle-small\") {\n <svg\n id=\"exclamation-mark-circle-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M8 10.5c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1M8 9c-.55 0-1-.45-1-1V4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1\"\n />\n <path d=\"M15 8A7 7 0 1 0 1 8a7 7 0 0 0 14 0m1 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0\" />\n </svg>\n }\n\n @if (icon === \"eye\") {\n <svg\n id=\"eye\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.75 10.23c1.451-.01 2.894.21 4.274.654l.14.045.094.114a5.737 5.737 0 1 1-8.87 0l.387.317-.151-.476a13.863 13.863 0 0 1 4.127-.653Zm-3.68 1.562a4.737 4.737 0 1 0 7.505-.001 12.596 12.596 0 0 0-3.817-.56h-.001c-1.25.007-2.492.196-3.686.56Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M15.807 12.01a2.667 2.667 0 1 0 2.667 2.667 2.656 2.656 0 0 0-2.667-2.666Z\" />\n <path\n fill-rule=\"evenodd\"\n d=\"M6.307 15.915Zm.143-.235a7.3 7.3 0 0 1 .34-.478 9.683 9.683 0 0 1 1.62-1.638c1.49-1.19 3.854-2.397 7.344-2.397s5.854 1.206 7.344 2.397a9.686 9.686 0 0 1 1.62 1.638c.148.192.26.356.34.478a7.37 7.37 0 0 1-.341.478 9.715 9.715 0 0 1-1.623 1.638c-1.491 1.191-3.856 2.397-7.34 2.397-3.485 0-5.849-1.206-7.34-2.397a9.717 9.717 0 0 1-1.623-1.638 7.327 7.327 0 0 1-.34-.478Zm19.192 0c.442-.234.442-.234.441-.235v-.002l-.003-.005-.008-.014a3.061 3.061 0 0 0-.136-.227 8.103 8.103 0 0 0-.426-.605 10.678 10.678 0 0 0-1.788-1.81c-1.646-1.315-4.226-2.615-7.968-2.615s-6.322 1.3-7.968 2.616a10.678 10.678 0 0 0-1.788 1.809 8.111 8.111 0 0 0-.533.781 3.033 3.033 0 0 0-.029.05l-.008.015-.002.005-.001.002.44.235-.44-.234-.125.235.124.234.442-.235-.442.235.001.001.001.002.003.004.008.015a3.798 3.798 0 0 0 .135.226c.095.15.236.358.428.606.383.496.969 1.153 1.79 1.808 1.647 1.316 4.227 2.616 7.964 2.616s6.316-1.3 7.964-2.616a10.719 10.719 0 0 0 1.79-1.808 8.113 8.113 0 0 0 .535-.782l.028-.05.008-.015.003-.004v-.002s.001-.001-.44-.236Zm0 0 .441.235.125-.234-.125-.236-.441.235Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"eye-blind\") {\n <svg\n id=\"eye-blind\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M15.8 11.2c-2.1 0-3.8.4-5.1 1-.9 1.8-.7 4 .4 5.7l.7-.7c-1-1.6-1-3.8.3-5.4 1.2-.4 2.4-.6 3.7-.6 0 .1.1.1.1.1.6 0 1.2 0 1.8.1l.1-.1c-.7-.1-1.4-.1-2-.1ZM20.5 12l-.5.5c1 1.9.6 4.2-1 5.7-.9.9-2.1 1.3-3.2 1.3-.8 0-1.5-.2-2.2-.5l-.7.7c.3.2.6.3.9.4.6.1 1.2.1 1.9.1.8 0 1.5-.1 2.2-.2.7-.3 1.3-.6 1.8-1.2 1.8-1.7 2.3-4.4 1.3-6.6-.1 0-.3-.1-.5-.2Z\"\n />\n <path\n d=\"M15.8 12c-1.5 0-2.7 1.2-2.7 2.7 0 .4.1.7.2 1l3.5-3.5c-.3-.1-.6-.2-1-.2ZM18.4 14.1l-3.2 3.2c.2 0 .4.1.6.1 1.5 0 2.7-1.2 2.7-2.7 0-.2 0-.4-.1-.6Z\"\n />\n <path\n d=\"M15.8 10.2c-3.7 0-6.3 1.3-8 2.6-.8.6-1.4 1.3-1.8 1.8-.2.2-.3.5-.4.6l-.1.2v.1l.4.2-.4.2v.1l.1.2c.1.1.2.4.4.6.4.5 1 1.2 1.8 1.8.5.4 1 .7 1.6 1.1l.7-.7c-.7-.3-1.2-.7-1.7-1.1-.7-.6-1.3-1.2-1.6-1.6-.1-.2-.3-.4-.3-.5.1-.1.2-.3.3-.5.3-.4.9-1 1.6-1.6.6-.5 1.3-1 2.2-1.4 1.3-.6 3-1 5.1-1 .7 0 1.3 0 1.9.1l.9-.9c-.8-.2-1.7-.3-2.7-.3ZM26.1 15.4l-.4.2.4.2.1-.2-.1-.2Zm-4.9-4.1-.7.7c.2.1.4.2.6.2.8.4 1.5.8 2.1 1.3.7.6 1.3 1.2 1.6 1.6.1.2.3.4.3.5-.1.1-.2.3-.3.5-.3.4-.9 1-1.6 1.6-1.2.9-2.8 1.9-5.2 2.2-.7.1-1.4.2-2.2.2-.7 0-1.3 0-1.9-.1-.4-.1-.8-.1-1.2-.2l-.8.8c1.1.3 2.4.5 3.9.5 3.7 0 6.3-1.3 8-2.6.8-.7 1.4-1.3 1.8-1.8.2-.2.3-.5.4-.6v-.2l-.4-.2.4-.2v-.1l-.1-.2c-.1-.1-.2-.4-.4-.6-.4-.5-1-1.2-1.8-1.8-.6-.6-1.5-1.1-2.5-1.5Z\"\n />\n <path d=\"m5.9 15.7-.4-.2-.1.2.1.2.4-.2Z\" />\n </svg>\n }\n\n @if (icon === \"fail\") {\n <svg\n id=\"fail\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M12.11 9.934a1.545 1.545 0 1 0-2.184 2.168l10.06 10.053c.284.289.67.454 1.075.461h.033c.4-.01.783-.171 1.07-.451l.007-.007.007-.007c.586-.6.586-1.557 0-2.157L12.11 9.934Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M20.093 9.724a1.553 1.553 0 0 1 2.194 2.17l-.028.035L12.02 22.162c-.289.282-.673.444-1.077.454h-.033a1.553 1.553 0 0 1-1.084-2.637l.006-.006L20.061 9.75l.032-.026Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"hourglass\") {\n <svg\n id=\"hourglass\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m16.896 16 3.904-2.848V9.6h1.067V8.533H10.134V9.6H11.2v3.552L15.095 16 11.2 18.848V22.4h-1.067v1.067h11.733V22.4H20.8v-3.552L16.896 16Zm-4.628-3.392L16 15.338l3.733-2.73V9.6h-7.466v3.008Zm7.466 9.792v-3.008l-3.733-2.73-3.733 2.73V22.4h7.466Zm-6.4-10.336v-.33h5.333v.33L16 14.016l-2.666-1.952Zm5.333 8.203h-5.333v1.066h5.333v-1.066Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"increase-indent\") {\n <svg\n id=\"increase-indent\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13.333 16.533h9.6v-1.067h-9.6v1.067ZM13.333 18.667h9.6V17.6h-9.6v1.067ZM9.066 20.8h13.867v-1.067H9.066V20.8ZM13.333 14.4h9.6v-1.067h-9.6V14.4ZM9.066 12.267h13.867V11.2H9.066v1.067ZM9.066 18.133l3.2-2.133-3.2-2.133v4.266Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"info\") {\n <svg\n id=\"info\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <circle cx=\"16\" cy=\"11.5\" r=\"1.5\" />\n <path d=\"M17 20.5v-6h-3v1h1v5h-1v1h4v-1h-1Z\" />\n </svg>\n }\n\n @if (icon === \"info-circle\") {\n <svg\n id=\"info-circle\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 25a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0 1c5.523 0 10-4.477 10-10S21.523 6 16 6 6 10.477 6 16s4.477 10 10 10Z\"\n clip-rule=\"evenodd\"\n />\n <circle cx=\"16\" cy=\"11.5\" r=\"1.5\" />\n <path d=\"M17 20.5v-6h-3v1h1v5h-1v1h4v-1h-1Z\" />\n </svg>\n }\n\n @if (icon === \"info-circle-fill\") {\n <svg\n id=\"info-circle-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path\n fill=\"#fff\"\n d=\"M26 16c0 5.523-4.477 10-10 10S6 21.523 6 16 10.477 6 16 6s10 4.477 10 10Z\"\n />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M16 25a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0 1c5.523 0 10-4.477 10-10S21.523 6 16 6 6 10.477 6 16s4.477 10 10 10Z\"\n clip-rule=\"evenodd\"\n />\n <circle cx=\"16\" cy=\"11.5\" r=\"1.5\" fill=\"#1076DB\" />\n <path fill=\"#1076DB\" d=\"M17 20.5v-6h-3v1h1v5h-1v1h4v-1h-1Z\" />\n </svg>\n }\n\n @if (icon === \"info-circle-small\") {\n <svg\n id=\"info-circle-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path d=\"M15 8A7 7 0 1 0 1 8a7 7 0 0 0 14 0m1 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0\" />\n <path d=\"M8 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3M9 12V7H6v1h1v4H6v1h4v-1z\" />\n </svg>\n }\n\n @if (icon === \"junction\") {\n <svg\n id=\"junction\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M19.2 8.565h-8.533L6.4 11.232l4.267 2.667H19.2V8.565ZM22.294 14.965h-8.533V20.3h8.533l4.267-2.667-4.267-2.667ZM16.427 21.365a.528.528 0 0 0-.533.534v3.2c0 .298.234.533.533.533a.528.528 0 0 0 .533-.533v-3.2a.528.528 0 0 0-.533-.534ZM16.427 7.477a.528.528 0 0 0 .533-.533V5.867a.528.528 0 0 0-.533-.534.528.528 0 0 0-.533.534v1.077c0 .299.234.533.533.533Z\"\n />\n </svg>\n }\n\n @if (icon === \"link\") {\n <svg\n id=\"link\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M20.707 11.121a1 1 0 0 0-1.414 0l-.354.354a1 1 0 0 1-1.414-1.414l.354-.354a3 3 0 0 1 4.242 0l.707.707a3 3 0 0 1 0 4.243l-4.242 4.242a2.5 2.5 0 0 1-3.536 0 1 1 0 1 1 1.415-1.414.5.5 0 0 0 .707 0l4.242-4.242a1 1 0 0 0 0-1.415l-.707-.707Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M11.868 21.374a1 1 0 0 0 1.414 0l.354-.353a1 1 0 0 1 1.414 1.414l-.354.354a3 3 0 0 1-4.242 0l-.707-.708a3 3 0 0 1 0-4.242l3.889-3.89a3 3 0 0 1 4.242 0l.354.354a1 1 0 1 1-1.414 1.414l-.354-.353a1 1 0 0 0-1.414 0l-3.889 3.89a1 1 0 0 0 0 1.413l.707.707Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"list-add\") {\n <svg\n id=\"list-add\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M12.5 6.5a.5.5 0 0 1 .5.5v2h2.5V7a.5.5 0 0 1 1 0v2H19V7a.5.5 0 0 1 1 0v2h1a1 1 0 0 1 1 1v1a4.978 4.978 0 0 0-3 1h-6.5a.5.5 0 0 0 0 1H18a4.977 4.977 0 0 0-.9 2h-4.6a.5.5 0 0 0 0 1H17a5 5 0 0 0 5 5v1a1 1 0 0 1-1 1H11a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1h1V7a.5.5 0 0 1 .5-.5Zm0 11.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22 12.5a.5.5 0 0 1 .5.5v2.5H25a.5.5 0 0 1 0 1h-2.5V19a.5.5 0 0 1-1 0v-2.5H19a.5.5 0 0 1 0-1h2.5V13a.5.5 0 0 1 .5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"list-minus\") {\n <svg\n id=\"list-minus\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M12.5 6.5a.5.5 0 0 1 .5.5v2h2.5V7a.5.5 0 0 1 1 0v2H19V7a.5.5 0 0 1 1 0v2h1a1 1 0 0 1 1 1v1a4.978 4.978 0 0 0-3 1h-6.5a.5.5 0 0 0 0 1H18a4.977 4.977 0 0 0-.9 2h-4.6a.5.5 0 0 0 0 1H17a5 5 0 0 0 5 5v1a1 1 0 0 1-1 1H11a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1h1V7a.5.5 0 0 1 .5-.5Zm0 11.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M18.5 16a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"lock\") {\n <svg\n id=\"lock\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M20.8 13.43h-1.066v-.534a3.733 3.733 0 1 0-7.467 0v.533h-1.066c-.59 0-1.067.478-1.067 1.067v6.933a1.6 1.6 0 0 0 1.6 1.6h8.533a1.6 1.6 0 0 0 1.6-1.6v-6.933c0-.59-.477-1.067-1.067-1.067Zm-7.466-.534a2.667 2.667 0 1 1 5.333 0v.533h-5.333v-.533Zm6.933 9.067a.533.533 0 0 0 .533-.534v-6.933h-9.6v6.933a.544.544 0 0 0 .534.534h8.533Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M17.6 17.163a1.6 1.6 0 1 0-2.4 1.376v1.557a.8.8 0 1 0 1.6 0v-1.557a1.59 1.59 0 0 0 .8-1.376Z\"\n />\n </svg>\n }\n\n @if (icon === \"lock-open\") {\n <svg\n id=\"lock-open\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M12.267 12.279v-.06 3.344H20.8c.59 0 1.067.477 1.067 1.066v6.934a1.6 1.6 0 0 1-1.6 1.6h-8.533a1.6 1.6 0 0 1-1.6-1.6v-6.934c0-.589.477-1.066 1.066-1.066h1.067v-3.284Zm8 11.817a.533.533 0 0 0 .533-.533v-6.934h-9.6v6.934a.544.544 0 0 0 .534.533h8.533Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M17.6 19.296a1.6 1.6 0 1 0-2.4 1.376v1.557a.8.8 0 1 0 1.6 0v-1.557a1.59 1.59 0 0 0 .8-1.376Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M12.268 16.106V11.66C12.268 9.708 13.976 8 16 8c2.025 0 3.733 1.708 3.733 3.66v.073h-1.067v-.073c0-1.363-1.23-2.593-2.666-2.593s-2.667 1.23-2.667 2.593v4.446h-1.066Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"magic-wand\") {\n <svg\n id=\"magic-wand\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m14.264 16.686-7.643 7.717a.722.722 0 0 0 0 1.04c.147.148.294.148.588.148s.44 0 .588-.149l7.643-7.717a.722.722 0 0 0 0-1.039c-.294-.296-.882-.296-1.176 0\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M19.703 13.125c.147 0 .441 0 .588-.149l2.94-2.968a.722.722 0 0 0 0-1.039.706.706 0 0 0-1.03 0l-2.939 2.968c-.294.149-.294.594-.147.89.147.15.441.298.588.298M20.29 16.686a.706.706 0 0 0-1.028 0 .722.722 0 0 0 0 1.04l2.94 2.967c.146.149.293.149.587.149s.441 0 .588-.149a.722.722 0 0 0 0-1.039l-3.087-2.968ZM12.5 8.82a.706.706 0 0 0-1.028 0 .722.722 0 0 0 0 1.04l2.94 2.968c.146.148.293.148.587.148s.441 0 .588-.148a.722.722 0 0 0 0-1.039L12.5 8.821ZM17.351 17.577c-.44 0-.735.296-.735.742v4.007c0 .445.294.742.735.742s.735-.297.735-.742v-4.007c0-.445-.44-.742-.735-.742\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M17.351 6.446c-.44 0-.735.297-.735.742v4.007c0 .445.294.742.735.742s.735-.296.735-.742V7.188c0-.445-.44-.742-.735-.742\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M9.854 15.5h3.969c.44 0 .735-.298.735-.743s-.441-.742-.735-.742H9.854c-.44 0-.735.297-.735.742s.294.742.735.742\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M24.847 14.015h-3.969c-.44 0-.734.297-.734.742s.294.742.735.742h3.968c.441 0 .735-.297.735-.742s-.441-.742-.735-.742\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"mail\") {\n <svg\n id=\"mail\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M24 10H8l8 7 8-7Z\" />\n <path d=\"m16 18.5-8-7V21a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-9.5l-8 7Z\" />\n </svg>\n }\n\n @if (icon === \"menu\") {\n <svg\n id=\"menu\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M28.8 9.067H3.2V5.333h25.6v3.734Zm0 4.8H3.2V17.6h25.6v-3.733Zm0 8.533H3.2v3.733h25.6V22.4Z\"\n />\n </svg>\n }\n\n @if (icon === \"message\") {\n <svg\n id=\"message\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M11 25.5V22h3.5L11 25.5Z\" />\n <path\n fill-rule=\"evenodd\"\n d=\"M7 11a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2v-9Zm3 5.75a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1-.75-.75Zm.75-4.75a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5h-10.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"minus\") {\n <svg\n id=\"minus\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M10 15h12v1H10z\" />\n </svg>\n }\n\n @if (icon === \"minus-ring-fill\") {\n <svg\n id=\"minus-ring-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path fill=\"#fff\" d=\"M29 16c0 7.18-5.82 13-13 13S3 23.18 3 16 8.82 3 16 3s13 5.82 13 13Z\" />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path fill=\"#1076DB\" d=\"M10 15h12v1H10z\" />\n </svg>\n }\n\n @if (icon === \"new-tab\") {\n <svg\n id=\"new-tab\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M17.571 8H23.464c.296 0 .536.24.536.536V14.429h-1.071v-4.6l-7.657 7.657-.38.379-.757-.758.38-.379 7.656-7.657h-4.6V8ZM9 10.143h7.5v1.071h-6.429V21.93h10.715V15.5h1.071V23H9V10.143Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"notebook\") {\n <svg\n id=\"notebook\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13 7a.5.5 0 0 0-1 0v2h-1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V10a1 1 0 0 0-1-1h-1V7a.5.5 0 0 0-1 0v2h-2.5V7a.5.5 0 0 0-1 0v2H13V7Zm-1 11.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5Zm.5-3.5a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7Zm-.5-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"notification\") {\n <svg\n id=\"notification\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 24a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm0 1a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z\"\n clip-rule=\"evenodd\"\n />\n <g clip-path=\"url(#a)\">\n <circle cx=\"16\" cy=\"11.5\" r=\"1.5\" />\n <path d=\"M17 20.5v-6h-3v1h1v5h-1v1h4v-1h-1Z\" />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M8 8h16v16H8z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"numbering\") {\n <svg\n id=\"numbering\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M8.897 8v4.732H7.812V9.078l-1.015.623-.483-.735L7.91 8h.987ZM7.705 13.902c.34 0 .632.06.875.182.247.121.434.285.56.49.13.205.196.432.196.679 0 .252-.051.492-.154.721-.103.229-.285.492-.546.791-.261.294-.642.667-1.14 1.12h1.938l-.112.847h-3.08v-.784c.574-.565.992-.99 1.253-1.274.261-.29.443-.53.546-.721.107-.191.161-.387.161-.588 0-.191-.056-.34-.168-.448a.582.582 0 0 0-.441-.168.845.845 0 0 0-.448.119 1.61 1.61 0 0 0-.413.371l-.65-.518a2.15 2.15 0 0 1 .699-.602c.275-.145.583-.217.924-.217ZM7.701 19.902c.331 0 .614.054.847.161.238.107.418.252.539.434.126.182.189.385.189.609 0 .285-.082.523-.245.714-.159.191-.385.329-.679.413.317.033.576.147.777.343.2.191.301.464.301.819 0 .266-.07.511-.21.735-.14.22-.348.394-.623.525-.27.13-.595.196-.973.196-.69 0-1.232-.238-1.624-.714l.595-.581c.159.159.313.275.462.35.154.07.322.105.504.105.22 0 .397-.06.532-.182.135-.121.203-.29.203-.504 0-.243-.065-.415-.196-.518-.126-.107-.317-.161-.574-.161h-.392l.119-.763h.273c.21 0 .376-.054.497-.161.121-.107.182-.259.182-.455a.49.49 0 0 0-.168-.399c-.112-.098-.266-.147-.462-.147-.331 0-.64.138-.924.413l-.546-.595a2.24 2.24 0 0 1 1.596-.637Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M12.347 10.232a.5.5 0 0 1 .5-.5h12.5a.5.5 0 0 1 0 1h-12.5a.5.5 0 0 1-.5-.5ZM12.347 16.732a.5.5 0 0 1 .5-.5h12.5a.5.5 0 0 1 0 1h-12.5a.5.5 0 0 1-.5-.5ZM12.347 22.732a.5.5 0 0 1 .5-.5h12.5a.5.5 0 0 1 0 1h-12.5a.5.5 0 0 1-.5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"pdf\") {\n <svg\n id=\"pdf\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M7.69 12.363c1.857 0 2.987.757 2.987 2.346 0 1.696-1.226 2.454-2.89 2.454h-.864v2.57H5.515v-7.37H7.69Zm-.01 3.744c.939 0 1.525-.32 1.525-1.398 0-.928-.565-1.312-1.525-1.312h-.757v2.71h.757Zm5.952-3.744c1.941 0 3.776.597 3.776 3.648 0 2.986-1.824 3.722-3.584 3.722h-2.006v-7.37h1.814Zm-.405 1.056v5.258h.693c1.099 0 2.005-.49 2.005-2.666 0-2.219-.96-2.592-2.048-2.592h-.65Zm9.525-.011h-2.613v2.208h2.272v1.045h-2.272v3.072H18.73v-7.37h4.17l-.149 1.045Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M7.5 6A1.5 1.5 0 0 1 9 4.5h15A1.5 1.5 0 0 1 25.5 6v16.207L20.707 27H9a1.5 1.5 0 0 1-1.5-1.5V21h1v4.5a.5.5 0 0 0 .5.5h11v-3a1.5 1.5 0 0 1 1.5-1.5h3V6a.5.5 0 0 0-.5-.5H9a.5.5 0 0 0-.5.5v5h-1V6Zm16.293 16.5H21.5a.5.5 0 0 0-.5.5v2.293l2.793-2.793Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"paperclip\") {\n <svg\n id=\"paperclip\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M11 8.594a3.594 3.594 0 1 1 7.188 0V21.53a2.156 2.156 0 0 1-4.313 0V11.47a.719.719 0 1 1 1.438 0V21.53a.719.719 0 1 0 1.437 0V8.594a2.157 2.157 0 0 0-4.312 0V21.53a3.593 3.593 0 1 0 7.187 0V11.47a.719.719 0 1 1 1.438 0V21.53a5.032 5.032 0 0 1-10.063 0z\"\n />\n </svg>\n }\n\n @if (icon === \"people\") {\n <svg\n id=\"people\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"34\"\n height=\"33\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M25 17.197a6 6 0 1 0-6.367-10.164A6 6 0 0 1 21 18.197V19h2a4 4 0 0 1 4 4v1h4v-2a4 4 0 0 0-4-4h-2v-.803ZM14.353 19H15v-.465c-.231.134-.448.29-.646.465Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22 14c0 2.22-1.207 4.16-3 5.197V20h2a4 4 0 0 1 4 4v2H7v-2a4 4 0 0 1 4-4h2v-.803A6 6 0 1 1 22 14Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"person\") {\n <svg\n id=\"person\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M22 13c0 2.22-1.207 4.16-3 5.197V19h2a4 4 0 0 1 4 4v2H7v-2a4 4 0 0 1 4-4h2v-.803A6 6 0 1 1 22 13Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"person-small\") {\n <svg\n id=\"person-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13 6a4.993 4.993 0 0 1-2 4v1h1a4 4 0 0 1 4 4v1H0v-1a4 4 0 0 1 4-4h1v-1a5 5 0 1 1 8-4Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"picker\") {\n <svg\n id=\"picker\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16.794 14.851a3.396 3.396 0 0 1 3.392-3.392 3.395 3.395 0 0 1 3.393 3.392 3.396 3.396 0 0 1-3.393 3.392 3.396 3.396 0 0 1-3.392-3.392m10.408 6.512-3.01-3.122a5.22 5.22 0 0 0 1.247-3.39A5.258 5.258 0 0 0 20.186 9.6a5.257 5.257 0 0 0-5.252 5.251 5.258 5.258 0 0 0 5.252 5.25c.932 0 1.807-.246 2.565-.673l3.093 3.208c.361.372.96.383 1.334.023a.915.915 0 0 0 .024-1.296\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M3.2 17.067h8.533V16H3.2v1.067ZM3.2 12.8h9.6v-1.067H3.2V12.8ZM3.2 8.533h10.667V7.467H3.2v1.066ZM3.2 21.333h9.6v-1.066H3.2v1.066ZM3.2 25.6h8.533v-1.067H3.2V25.6Z\"\n />\n </svg>\n }\n\n @if (icon === \"pin-small\") {\n <svg\n id=\"pin-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M8.34 4C6.5 4 5 5.52 5 7.39c0 .6.16 1.23.43 1.67l.02.04c.07.12.38.52.8 1 .86.99 2.11 2.33 2.11 2.33s1.24-1.36 2.09-2.34c.4-.47.7-.86.77-.97l.04-.07c.28-.44.43-1.07.43-1.67 0-1.86-1.5-3.38-3.35-3.38Zm0 5.24c-1.04 0-1.89-.85-1.89-1.89s.85-1.9 1.89-1.9 1.9.85 1.9 1.89-.85 1.9-1.9 1.9Z\"\n />\n </svg>\n }\n\n @if (icon === \"place\") {\n <svg\n id=\"place\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M21.867 8.533c0 .295-.219.534-.489.534H10.623c-.27 0-.49-.24-.49-.534 0-.294.22-.533.49-.533h10.755c.27 0 .49.239.49.533M15.623 11.89l-5.333 5.333a.534.534 0 0 0 .754.754l4.423-4.422v9.912a.533.533 0 1 0 1.067 0v-9.912l4.422 4.422a.534.534 0 0 0 .755-.754l-5.334-5.333a.534.534 0 0 0-.754 0\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"place-ring-fill\") {\n <svg\n id=\"place-ring-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path fill=\"#fff\" d=\"M29 16c0 7.18-5.82 13-13 13S3 23.18 3 16 8.82 3 16 3s13 5.82 13 13Z\" />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M23.467 21.733c-.294 0-.533-.219-.533-.489V10.49c0-.27.238-.489.533-.489.294 0 .533.219.533.489v10.755c0 .27-.239.49-.533.49ZM20.11 15.49l-5.333-5.334a.534.534 0 0 0-.754.754l4.422 4.424H8.532a.533.533 0 1 0 0 1.066h9.913l-4.422 4.423a.534.534 0 0 0 .754.754l5.333-5.334a.534.534 0 0 0 0-.754Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"plus\") {\n <svg\n id=\"plus\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M16 9h1v13h-1z\" />\n <path d=\"M10 15h13v1H10z\" />\n </svg>\n }\n\n @if (icon === \"print\") {\n <svg\n id=\"print\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <mask\n id=\"a\"\n width=\"20\"\n height=\"20\"\n x=\"6\"\n y=\"6\"\n maskUnits=\"userSpaceOnUse\"\n style=\"mask-type: luminance\"\n >\n <path fill-rule=\"evenodd\" d=\"M6.4 6.933h19.2v18.134H6.4V6.933Z\" clip-rule=\"evenodd\" />\n </mask>\n <path\n fill-rule=\"evenodd\"\n d=\"m21.654 24-11.414.107v-6.4c0-.214.107-.32.214-.32H21.44c.107 0 .214.106.214.32V24ZM10.24 8h11.414v1.813H10.24V8Zm12.587 16.107V22.08c.107 0 .213.107.32.107h1.173c.747 0 1.28-.64 1.28-1.387v-8.32c0-1.387-1.066-2.56-2.346-2.56h-.427V7.893c0-.533-.533-.96-1.067-.96H10.24c-.64 0-1.066.427-1.066.96V9.92h-.427c-1.28 0-2.347 1.173-2.347 2.56v8.213c0 .747.64 1.387 1.28 1.387h1.387v2.027c0 .533.533.96 1.067.96h11.413c.747 0 1.28-.427 1.28-.96Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M11.84 20.053h8.32v-1.066h-8.32v1.066ZM11.84 22.827h8.32V21.76h-8.32v1.067Z\" />\n </svg>\n }\n\n @if (icon === \"question-mark\") {\n <svg\n id=\"question-mark\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M15.992 7c.96 0 1.784.176 2.472.528.704.336 1.232.792 1.584 1.368.352.576.528 1.192.528 1.848 0 .624-.112 1.16-.336 1.608a3.336 3.336 0 0 1-.768 1.056c-.304.272-.712.592-1.224.96-.672.464-1.176.896-1.512 1.296-.336.384-.504.904-.504 1.56v1.08h-2.184v-1.2c0-.688.112-1.272.336-1.752.24-.48.52-.864.84-1.152.32-.288.744-.616 1.272-.984.608-.4 1.056-.76 1.344-1.08.288-.32.432-.736.432-1.248 0-.656-.216-1.16-.648-1.512-.432-.368-1.024-.552-1.776-.552-1.28 0-2.392.576-3.336 1.728L11 9.376C12.344 7.792 14.008 7 15.992 7ZM15.2 20.776c.48 0 .88.16 1.2.48.32.32.48.712.48 1.176 0 .464-.16.864-.48 1.2-.32.32-.72.48-1.2.48a1.6 1.6 0 0 1-1.176-.48 1.68 1.68 0 0 1-.48-1.2c0-.464.16-.856.48-1.176a1.6 1.6 0 0 1 1.176-.48Z\"\n />\n </svg>\n }\n\n @if (icon === \"question-mark-small\") {\n <svg\n id=\"question-mark-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M8.094 3c.616 0 1.139.103 1.568.308.439.205.77.48.994.826.224.345.336.723.336 1.134 0 .373-.065.695-.196.966-.13.261-.29.476-.476.644-.187.168-.43.355-.728.56-.383.252-.667.485-.854.7-.187.215-.28.5-.28.854v.476H6.792v-.546c0-.42.065-.775.196-1.064.14-.299.303-.532.49-.7.187-.177.43-.369.728-.574.317-.215.546-.401.686-.56a.862.862 0 0 0 .224-.616c0-.308-.107-.546-.322-.714-.205-.177-.49-.266-.854-.266-.663 0-1.255.308-1.778.924L5 4.442C5.812 3.481 6.843 3 8.094 3Zm-.448 7.826c.317 0 .588.112.812.336.224.215.336.48.336.798 0 .308-.112.574-.336.798a1.106 1.106 0 0 1-.812.336 1.09 1.09 0 0 1-.798-.336 1.114 1.114 0 0 1-.322-.798c0-.317.107-.583.322-.798a1.06 1.06 0 0 1 .798-.336Z\"\n />\n </svg>\n }\n\n @if (icon === \"required\") {\n <svg\n id=\"required\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.745 9a.75.75 0 0 1 .75.75v4.676l3.87-2.32a.75.75 0 0 1 .77 1.287l-3.931 2.357 3.932 2.357a.75.75 0 1 1-.771 1.286l-3.87-2.319v4.676a.75.75 0 0 1-1.5 0v-4.675l-3.859 2.318a.75.75 0 0 1-.772-1.286l3.925-2.357-3.925-2.357a.75.75 0 0 1 .772-1.286l3.86 2.318V9.75a.75.75 0 0 1 .75-.75Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"ring-close\") {\n <svg\n id=\"ring-close\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M20.255 20.932a.672.672 0 0 1-.47-.202l-8.533-8.534a.672.672 0 1 1 .95-.938l8.533 8.533a.672.672 0 0 1 0 .939.715.715 0 0 1-.48.202Z\"\n />\n <path\n d=\"M11.721 20.932a.714.714 0 0 0 .48-.202l8.533-8.534a.672.672 0 0 0-.949-.938l-8.533 8.533a.672.672 0 0 0 .469 1.141Z\"\n />\n </svg>\n }\n\n @if (icon === \"ring-close-fill\") {\n <svg\n id=\"ring-close-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path\n fill=\"#fff\"\n d=\"M29 16C29 23.1797 23.1797 29 16 29C8.8203 29 3 23.1797 3 16C3 8.8203 8.8203 3 16 3C23.1797 3 29 8.8203 29 16Z\"\n />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M16 28C22.6274 28 28 22.6274 28 16C28 9.37258 22.6274 4 16 4C9.37258 4 4 9.37258 4 16C4 22.6274 9.37258 28 16 28ZM16 29C23.1797 29 29 23.1797 29 16C29 8.8203 23.1797 3 16 3C8.8203 3 3 8.8203 3 16C3 23.1797 8.8203 29 16 29Z\"\n />\n <path\n fill=\"#1076DB\"\n d=\"M20.2545 20.9324C20.0775 20.9294 19.9088 20.8565 19.7852 20.7298L11.2518 12.1964C11.05 12.0347 10.9589 11.7714 11.0176 11.5195C11.0763 11.2676 11.2744 11.0717 11.5269 11.0159C11.7795 10.96 12.0417 11.0541 12.2012 11.2578L20.7345 19.7911C20.9892 20.0522 20.9892 20.4687 20.7345 20.7298C20.6057 20.8553 20.4343 20.9277 20.2545 20.9324Z\"\n />\n <path\n fill=\"#1076DB\"\n d=\"M11.721 20.9324C11.9008 20.9277 12.0723 20.8553 12.201 20.7298L20.7344 12.1964C20.9447 11.9278 20.9203 11.5442 20.6777 11.3044C20.4351 11.0645 20.0513 11.0445 19.785 11.2578L11.2517 19.7911C11.0643 19.9831 11.009 20.268 11.1111 20.5161C11.2131 20.7642 11.4528 20.9279 11.721 20.9324V20.9324Z\"\n />\n </svg>\n }\n\n @if (icon === \"ring-plus-fill\") {\n <svg\n id=\"ring-plus-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path fill=\"#fff\" d=\"M29 16c0 7.18-5.82 13-13 13S3 23.18 3 16 8.82 3 16 3s13 5.82 13 13Z\" />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path fill=\"#1076DB\" d=\"M16 9h1v13h-1z\" />\n <path fill=\"#1076DB\" d=\"M10 15h13v1H10z\" />\n </svg>\n }\n\n @if (icon === \"ring-plus\") {\n <svg\n id=\"ring-plus\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M16 9h1v13h-1z\" />\n <path d=\"M10 15h13v1H10z\" />\n </svg>\n }\n\n @if (icon === \"rosette\") {\n <svg\n id=\"rosette\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16.439 20.144a5.925 5.925 0 0 1-5.92-5.919 5.925 5.925 0 0 1 5.92-5.919 5.926 5.926 0 0 1 5.92 5.92 5.926 5.926 0 0 1-5.92 5.918Zm9.385-5.862a.754.754 0 0 0 .532-.305.655.655 0 0 0 .112-.53l-.61-2.758a.656.656 0 0 0-.327-.432.746.746 0 0 0-.61-.05l-2.295.786c-.158-.3-.339-.586-.537-.86l1.73-1.705a.755.755 0 0 0 .227-.572.655.655 0 0 0-.242-.483L21.61 5.596a.65.65 0 0 0-.524-.136.753.753 0 0 0-.51.34L19.27 7.845a6.993 6.993 0 0 0-1.017-.357l.273-2.41a.756.756 0 0 0-.182-.586.654.654 0 0 0-.493-.224h-2.824a.654.654 0 0 0-.492.224.752.752 0 0 0-.183.585l.273 2.411c-.349.094-.687.21-1.01.354l-1.304-2.04a.751.751 0 0 0-.512-.34.65.65 0 0 0-.523.135L9.082 7.373a.655.655 0 0 0-.242.483c-.011.211.071.42.226.572l1.727 1.7c-.199.274-.381.561-.541.863l-2.287-.785a.748.748 0 0 0-.611.051.657.657 0 0 0-.327.432l-.61 2.759a.65.65 0 0 0 .112.529c.123.17.318.281.533.305l2.407.255c.013.28.04.554.085.824l-2.304.748a.747.747 0 0 0-.457.41.65.65 0 0 0 0 .54l1.169 2.571c.077.17.227.299.41.356a.754.754 0 0 0 .606-.078l2.074-1.24c.234.283.493.54.767.784l-3.375 5.905 3.83-.958.996 2.987 3.047-5.338 3.046 5.338.998-2.987 3.83.958-3.293-5.76c.338-.28.65-.592.93-.931l2.078 1.241a.752.752 0 0 0 .607.08.666.666 0 0 0 .41-.357l1.17-2.572a.65.65 0 0 0 0-.538.75.75 0 0 0-.459-.411l-2.309-.75a7.17 7.17 0 0 0 .084-.821l2.415-.256Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"rule\") {\n <svg\n id=\"rule\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.467 8.533v7.574a2.666 2.666 0 0 0 0 5.226v2.134h1.067V21.28a2.666 2.666 0 0 0 0-5.227v-7.52h-1.067Zm-4.265 14.934v-6.454a2.666 2.666 0 0 0 0-5.226V9.6h-1.067v2.24a2.666 2.666 0 0 0 0 5.227v6.4h1.067ZM9.069 14.4a1.6 1.6 0 1 1 3.2 0 1.6 1.6 0 0 1-3.2 0Zm12.263-1.6a1.6 1.6 0 1 0 0 3.2 1.6 1.6 0 0 0 0-3.2Zm-.534 10.667h1.067v-6.454a2.666 2.666 0 0 0 0-5.226V9.6h-1.067v2.24a2.666 2.666 0 0 0 0 5.227v6.4ZM16 20.267a1.6 1.6 0 1 0 0-3.2 1.6 1.6 0 0 0 0 3.2Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"search\") {\n <svg\n id=\"search\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m19.114 18.485 3.083 3.2a.917.917 0 0 1 0 1.227.981.981 0 0 1-1.366 0l-3.2-3.2a5.472 5.472 0 0 1-2.634.683 5.397 5.397 0 1 1 5.397-5.333 5.227 5.227 0 0 1-1.28 3.423Zm-5.473-6.65a3.488 3.488 0 0 0-2.11 3.27 3.467 3.467 0 0 0 3.476 3.423 3.488 3.488 0 1 0-1.366-6.693Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"seats\") {\n <svg\n id=\"seats\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M9.602 8.001c-.882 0-1.6.717-1.6 1.599s.718 1.6 1.599 1.6c.882 0 1.601-.717 1.601-1.599s-.718-1.6-1.6-1.6m0 4.267A2.67 2.67 0 0 1 6.935 9.6a2.67 2.67 0 0 1 2.666-2.667 2.67 2.67 0 0 1 2.667 2.668A2.67 2.67 0 0 1 9.6 12.268M16.002 8.002c-.882 0-1.6.717-1.6 1.6a1.6 1.6 0 0 0 1.6 1.599c.427 0 .83-.166 1.132-.468a1.602 1.602 0 0 0-1.132-2.73m0 4.266a2.67 2.67 0 0 1-2.666-2.667 2.67 2.67 0 0 1 2.667-2.668 2.671 2.671 0 0 1 2.666 2.668 2.65 2.65 0 0 1-.78 1.886 2.65 2.65 0 0 1-1.886.78M9.601 14.401a1.601 1.601 0 0 0-.001 3.2c.882 0 1.601-.718 1.601-1.6 0-.882-.718-1.6-1.6-1.6m0 4.267A2.671 2.671 0 0 1 6.934 16 2.67 2.67 0 0 1 9.6 13.333a2.67 2.67 0 0 1 2.667 2.668A2.67 2.67 0 0 1 9.6 18.668M18.135 16.002A2.134 2.134 0 1 1 13.866 16a2.134 2.134 0 0 1 4.269.002\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M16.001 14.402a1.601 1.601 0 0 0 0 3.2c.881 0 1.6-.718 1.6-1.6 0-.882-.717-1.6-1.6-1.6m0 4.267A2.671 2.671 0 0 1 13.335 16a2.67 2.67 0 0 1 2.667-2.667 2.67 2.67 0 0 1 2.666 2.668 2.67 2.67 0 0 1-2.667 2.667\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22.402 8.003c-.882 0-1.6.717-1.6 1.6a1.6 1.6 0 0 0 1.599 1.599c.427 0 .83-.166 1.133-.468a1.602 1.602 0 0 0-1.132-2.73m0 4.266h-.001a2.67 2.67 0 0 1-2.666-2.667 2.67 2.67 0 0 1 2.667-2.668 2.671 2.671 0 0 1 2.667 2.668c0 .713-.278 1.383-.781 1.887a2.657 2.657 0 0 1-1.886.78\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M24.534 16.003a2.134 2.134 0 1 1-4.268-.002 2.134 2.134 0 0 1 4.268.002\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22.4 14.403a1.601 1.601 0 0 0 0 3.2c.882 0 1.6-.718 1.6-1.6 0-.882-.717-1.6-1.6-1.6m0 4.267a2.671 2.671 0 0 1-2.666-2.668 2.669 2.669 0 0 1 2.667-2.667 2.671 2.671 0 0 1 2.667 2.668A2.67 2.67 0 0 1 22.4 18.67M11.733 22.401a2.133 2.133 0 1 1-4.267 0 2.133 2.133 0 0 1 4.268 0\"\n clip-rule=\"evenodd\"\n />\n <mask\n id=\"b\"\n width=\"7\"\n height=\"7\"\n x=\"6\"\n y=\"19\"\n maskUnits=\"userSpaceOnUse\"\n style=\"mask-type: luminance\"\n >\n <path fill-rule=\"evenodd\" d=\"M6.934 19.734h5.333v5.334H6.934v-5.334Z\" clip-rule=\"evenodd\" />\n </mask>\n <g mask=\"url(#b)\">\n <path\n fill-rule=\"evenodd\"\n d=\"M9.599 20.8A1.595 1.595 0 0 0 8 22.4c0 .882.717 1.6 1.599 1.6s1.6-.717 1.601-1.6c0-.881-.718-1.6-1.6-1.6h-.001Zm0 4.268a2.668 2.668 0 0 1-1.885-4.554 2.65 2.65 0 0 1 1.884-.78H9.6a2.669 2.669 0 0 1 2.667 2.667 2.671 2.671 0 0 1-2.668 2.667Z\"\n clip-rule=\"evenodd\"\n />\n </g>\n <path\n fill-rule=\"evenodd\"\n d=\"M18.134 22.402a2.133 2.133 0 1 1-4.267 0 2.133 2.133 0 0 1 4.267 0\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M16 20.802a1.595 1.595 0 0 0-1.6 1.599c0 .882.717 1.6 1.6 1.601.881 0 1.6-.718 1.6-1.6 0-.882-.718-1.6-1.6-1.6Zm0 4.267a2.668 2.668 0 0 1-1.885-4.554 2.65 2.65 0 0 1 1.883-.78h.003a2.669 2.669 0 0 1 2.666 2.667A2.671 2.671 0 0 1 16 25.069ZM24.533 22.403a2.133 2.133 0 1 1-4.267 0 2.133 2.133 0 0 1 4.267 0\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22.399 20.803a1.595 1.595 0 0 0-1.6 1.599c0 .882.718 1.6 1.6 1.601.882 0 1.6-.718 1.6-1.6 0-.882-.717-1.6-1.6-1.6Zm0 4.267a2.668 2.668 0 0 1-1.885-4.554 2.65 2.65 0 0 1 1.884-.78h.002a2.669 2.669 0 0 1 2.667 2.667 2.671 2.671 0 0 1-2.668 2.667Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"settings\") {\n <svg\n id=\"settings\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M23.819 16.768v-1.344a1.067 1.067 0 0 0-.864-1.067l-1.259-.181a6.1 6.1 0 0 0-.352-.864l.758-1.013c.32-.425.28-1.021-.096-1.398l-.95-.949a1.067 1.067 0 0 0-1.397-.107l-1.067.768a5.509 5.509 0 0 0-.853-.352l-.181-1.258a1.067 1.067 0 0 0-1.067-.918h-1.344a1.067 1.067 0 0 0-1.067.918L13.9 10.26c-.294.094-.58.211-.853.352l-1.067-.768a1.067 1.067 0 0 0-1.397.107l-.95.95a1.067 1.067 0 0 0-.096 1.397l.758 1.013c-.14.279-.257.567-.352.864l-1.259.181c-.53.075-.923.532-.917 1.067v1.344c-.006.535.387.992.917 1.067l1.259.181c.095.293.213.578.352.853l-.758 1.067a1.067 1.067 0 0 0 .096 1.387l.95.96c.376.376.973.417 1.397.096l1.067-.768c.271.149.557.27.853.362l.181 1.259c.075.53.532.923 1.067.917h1.29c.536.006.992-.387 1.067-.917l.182-1.259c.296-.092.581-.213.853-.362l1.067.768c.424.32 1.02.28 1.397-.096l.95-.96c.37-.375.41-.965.095-1.387l-.757-1.067c.14-.275.257-.56.352-.853l1.259-.181c.53-.075.922-.532.917-1.067Zm-7.2 4.181-.267 1.867h-.981l-.267-1.867-.757-.245a5.093 5.093 0 0 1-.672-.288l-.715-.352-1.504 1.13-.693-.693 1.13-1.514-.362-.704a5.236 5.236 0 0 1-.277-.672l-.246-.758-1.866-.266v-.982l1.866-.266.246-.758c.074-.23.167-.456.277-.672l.363-.714-1.131-1.504.693-.694 1.504 1.131.715-.363c.215-.113.44-.206.672-.277l.757-.245.267-1.867h.981l.267 1.867.757.245c.233.07.458.162.672.277l.715.363 1.504-1.13.693.693-1.13 1.504.362.714c.11.216.204.441.278.672l.245.758 1.867.266v.982l-1.867.266-.245.758a5.25 5.25 0 0 1-.278.672l-.362.704 1.13 1.514-.693.694-1.504-1.131-.715.352c-.216.114-.44.21-.672.288l-.757.245Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M13.152 16.096a2.667 2.667 0 1 1 5.334 0 2.667 2.667 0 0 1-5.334 0Zm4 0a1.333 1.333 0 1 0-2.667 0 1.333 1.333 0 0 0 2.667 0Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"shopping-cart\") {\n <svg\n id=\"shopping-cart\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M22.524 9.674a1 1 0 0 1 .868 1.117L22.216 20.2H9.784l-1.027-8.194a.387.387 0 0 0-.384-.34h-3.04a1 1 0 1 1 0-2h3.04a2.387 2.387 0 0 1 2.368 2.093l.808 6.441h8.901l.957-7.657a1 1 0 0 1 1.117-.869ZM11.733 25.6a2.133 2.133 0 1 0 0-4.267 2.133 2.133 0 0 0 0 4.267ZM20.267 25.6a2.133 2.133 0 1 0 0-4.267 2.133 2.133 0 0 0 0 4.267Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22.4 12.233H9.6v-1h12.8v1ZM21.334 15.967H10.667v-1h10.667v1Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M13.366 18.667v-6.934h1v6.934h-1ZM17.634 18.667v-6.934h1v6.934h-1Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"sorter\") {\n <svg\n id=\"sorter\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16.146 10.646a.5.5 0 0 1 .708 0l4.5 4.5a.5.5 0 0 1-.708.708L17 12.207V20.5a.5.5 0 0 1-1 0v-8.293l-3.646 3.647a.5.5 0 0 1-.708-.708l4.5-4.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"star\") {\n <svg\n id=\"star\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m16.811 8.83 2.121 4.298 4.745.69a.533.533 0 0 1 .294.91l-3.432 3.345.81 4.724a.533.533 0 0 1-.773.562l-4.242-2.23-4.244 2.23a.532.532 0 0 1-.772-.562l.809-4.724-3.43-3.346a.533.533 0 0 1 .294-.91l4.743-.689 2.122-4.297a.532.532 0 0 1 .955 0\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"switch\") {\n <svg\n id=\"switch\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M17.597 14.933a.528.528 0 0 0-.533.534v2.666h-6.71l2.294-2.293a.528.528 0 0 0 0-.757.528.528 0 0 0-.758 0l-3.2 3.2a.481.481 0 0 0-.117.181.533.533 0 0 0 0 .405.38.38 0 0 0 .118.171l3.2 3.2a.5.5 0 0 0 .373.16.5.5 0 0 0 .373-.16.528.528 0 0 0 0-.757L10.354 19.2h7.243a.528.528 0 0 0 .534-.533v-3.2a.528.528 0 0 0-.534-.534Z\"\n />\n <path\n d=\"M24.488 12.47a.534.534 0 0 0 0-.406.488.488 0 0 0-.117-.17l-3.2-3.2a.528.528 0 0 0-.758 0 .528.528 0 0 0 0 .757l2.294 2.282h-7.243a.528.528 0 0 0-.533.534v3.2c0 .298.234.533.533.533a.528.528 0 0 0 .533-.533V12.8h6.71l-2.294 2.293a.528.528 0 0 0 0 .758c.107.096.246.149.384.149a.5.5 0 0 0 .374-.16l3.2-3.2a.38.38 0 0 0 .117-.17Z\"\n />\n </svg>\n }\n\n @if (icon === \"three-dots\") {\n <svg\n id=\"three-dots\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M8.96 16a3.2 3.2 0 1 1-6.4 0 3.2 3.2 0 0 1 6.4 0ZM16 12.8a3.2 3.2 0 1 0 0 6.4 3.2 3.2 0 0 0 0-6.4Zm7.04 3.2a3.2 3.2 0 1 1 6.4 0 3.2 3.2 0 0 1-6.4 0Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"three-dots-small\") {\n <svg\n id=\"three-dots-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M5 8.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM13.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3ZM8.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z\"\n />\n </svg>\n }\n\n @if (icon === \"waiting-approval\") {\n <svg\n id=\"waiting-approval\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g fill-rule=\"evenodd\" clip-path=\"url(#a)\" clip-rule=\"evenodd\">\n <path\n d=\"m10.896 16 3.904-2.848V9.6h1.067V8.533H4.134V9.6H5.2v3.552L9.094 16 5.2 18.848V22.4H4.134v1.067h11.733V22.4H14.8v-3.552L10.896 16Zm-4.628-3.392L10 15.338l3.733-2.73V9.6H6.268v3.008Zm7.466 9.792v-3.008l-3.733-2.73-3.733 2.73V22.4h7.466Zm-6.4-10.336v-.33h5.333v.33l-2.666 1.952-2.667-1.952Zm5.333 8.203H7.334v1.066h5.333v-1.066ZM26.573 11.18a1 1 0 0 1 .247 1.393l-5.713 8.181a1 1 0 0 1-1.564.095l-3.287-3.665a1 1 0 0 1 1.488-1.335l2.445 2.726 4.991-7.147a1 1 0 0 1 1.393-.248Z\"\n />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"waiting-decline\") {\n <svg\n id=\"waiting-decline\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g fill-rule=\"evenodd\" clip-path=\"url(#a)\" clip-rule=\"evenodd\">\n <path\n d=\"m10.896 16 3.904-2.848V9.6h1.067V8.533H4.134V9.6H5.2v3.552L9.094 16 5.2 18.848V22.4H4.134v1.067h11.733V22.4H14.8v-3.552L10.896 16Zm-4.628-3.392L10 15.338l3.733-2.73V9.6H6.268v3.008Zm7.466 9.792v-3.008l-3.733-2.73-3.733 2.73V22.4h7.466Zm-6.4-10.336v-.33h5.333v.33l-2.666 1.952-2.667-1.952Zm5.333 8.203H7.334v1.066h5.333v-1.066ZM17.293 11.293a1 1 0 0 1 1.414 0l3.793 3.793 3.793-3.793a1 1 0 0 1 1.414 1.414L23.914 16.5l3.793 3.793a1 1 0 0 1-1.414 1.414L22.5 17.914l-3.793 3.793a1 1 0 0 1-1.414-1.414l3.793-3.793-3.793-3.793a1 1 0 0 1 0-1.414Z\"\n />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"zoom-in\") {\n <svg\n id=\"zoom-in\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g clip-path=\"url(#a)\">\n <path\n fill-rule=\"evenodd\"\n d=\"M15.5 23a8.5 8.5 0 1 0 0-17 8.5 8.5 0 0 0 0 17Zm0 2C21.299 25 26 20.299 26 14.5S21.299 4 15.5 4 5 8.701 5 14.5 9.701 25 15.5 25Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M21.293 21.293a1 1 0 0 1 1.414 0l5 5a1 1 0 0 1-1.414 1.414l-5-5a1 1 0 0 1 0-1.414Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M15 8h1v13h-1z\" />\n <path d=\"M9 14h13v1H9z\" />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"zoom-out\") {\n <svg\n id=\"zoom-out\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g clip-path=\"url(#a)\">\n <path\n fill-rule=\"evenodd\"\n d=\"M15.5 23a8.5 8.5 0 1 0 0-17 8.5 8.5 0 0 0 0 17Zm0 2C21.299 25 26 20.299 26 14.5S21.299 4 15.5 4 5 8.701 5 14.5 9.701 25 15.5 25Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M21.293 21.293a1 1 0 0 1 1.414 0l5 5a1 1 0 0 1-1.414 1.414l-5-5a1 1 0 0 1 0-1.414Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M9 14h12v1H9z\" />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n</ng-container>\n", styles: [".fudis-icon{box-sizing:border-box;display:inline-flex;align-items:center;transition:all .6s ease}.fudis-icon__rotate__flip-180{transform:rotate(180deg)}.fudis-icon__rotate__cw-90{transform:rotate(90deg)}.fudis-icon__rotate__ccw-90{transform:rotate(-90deg)}.fudis-icon__lg{width:calc(2rem / var(--fudis-rem-multiplier));height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-icon__sm{width:calc(1rem / var(--fudis-rem-multiplier));height:calc(1rem / var(--fudis-rem-multiplier))}.fudis-icon__color__yellow{fill:#f1c40f}.fudis-icon__color__red{fill:#b83c2e}.fudis-icon__color__gray-dark{fill:#484848}.fudis-icon__color__gray-light{fill:#d4d4d4}.fudis-icon__color__primary{fill:var(--fudis-color-primary)}.fudis-icon__color__primary-dark{fill:var(--fudis-color-primary-dark)}.fudis-icon__color__green{fill:#1e874b}.fudis-icon__color__white{fill:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: IconComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-icon', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container>\n @if (icon === \"achievement\") {\n <svg\n id=\"achievement\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M24.373 12.384c-.277.894-.664 1.75-1.152 2.55a7.743 7.743 0 0 1-1.067-2.251 29.695 29.695 0 0 1 1.152-3.616 21.15 21.15 0 0 1 1.067 3.317ZM22.209 15.67a8.063 8.063 0 0 1-1.067 2.186 6.729 6.729 0 0 1-1.067-2.133 8.66 8.66 0 0 1 1.067-2.422c.49.722.85 1.523 1.067 2.368Z\"\n />\n <path\n d=\"M24.064 17.888a10.932 10.932 0 0 1-2.9-.47 9.271 9.271 0 0 1 2.132-1.813c.984.023 1.96.177 2.902.459a9.526 9.526 0 0 1-2.134 1.824ZM22.73 20.267a9.804 9.804 0 0 1-2.634 1.066 9.826 9.826 0 0 1-2.496-1.365 9.802 9.802 0 0 1 2.634-1.067c.892.336 1.732.796 2.496 1.366ZM18.826 15.403c.347.778.549 1.613.598 2.464-.181.576-2.027 2.133-2.027 2.133a7.06 7.06 0 0 1-.597-2.475c.192-.608 2.026-2.122 2.026-2.122Z\"\n />\n <path\n d=\"m18.112 19.339 3.307-1.664-2.496 2.282-4.587 2.592.416-1.216 3.36-1.994ZM7.66 12.576c.255.84.613 1.647 1.066 2.4.47-.648.83-1.369 1.067-2.133a29.196 29.196 0 0 0-1.067-3.467 21.546 21.546 0 0 0-1.067 3.2ZM9.792 15.67a8.064 8.064 0 0 0 1.067 2.186 6.73 6.73 0 0 0 1.066-2.133A8.663 8.663 0 0 0 10.86 13.3a7.56 7.56 0 0 0-1.067 2.368Z\"\n />\n <path\n d=\"M7.936 17.888c.984-.025 1.96-.183 2.901-.47a9.27 9.27 0 0 0-2.133-1.813c-.983.023-1.959.177-2.901.459a9.525 9.525 0 0 0 2.133 1.824ZM11.904 21.333a9.825 9.825 0 0 0 2.496-1.365 9.802 9.802 0 0 0-2.635-1.067c-.891.336-1.732.796-2.495 1.366.821.481 1.709.84 2.634 1.066ZM13.174 15.403a7.062 7.062 0 0 0-.598 2.464c.181.576 2.027 2.133 2.027 2.133.347-.782.55-1.62.597-2.475-.192-.608-2.026-2.122-2.026-2.122Z\"\n />\n <path d=\"m13.888 19.339-3.307-1.664 2.496 2.282 4.587 2.592-.406-1.216-3.37-1.994Z\" />\n </svg>\n }\n\n @if (icon === \"achievement-small\") {\n <svg\n id=\"achievement-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M14.569 5.603a8.87 8.87 0 0 1-.904 2 6.079 6.079 0 0 1-.837-1.766c.24-.964.543-1.912.904-2.837.35.843.63 1.713.837 2.603ZM12.87 8.18c-.19.61-.473 1.19-.837 1.715a5.282 5.282 0 0 1-.837-1.673c.18-.673.463-1.313.837-1.9.384.566.667 1.195.837 1.858Z\"\n />\n <path\n d=\"M14.327 9.92a8.577 8.577 0 0 1-2.276-.368 7.271 7.271 0 0 1 1.673-1.422 8.636 8.636 0 0 1 2.277.36 7.473 7.473 0 0 1-1.674 1.43ZM13.28 11.787a7.689 7.689 0 0 1-2.067.836 7.708 7.708 0 0 1-1.958-1.07 7.688 7.688 0 0 1 2.067-.838c.7.264 1.359.624 1.958 1.072ZM10.217 7.97c.271.612.43 1.266.468 1.934-.142.452-1.59 1.673-1.59 1.673a5.54 5.54 0 0 1-.468-1.941c.15-.477 1.59-1.665 1.59-1.665Z\"\n />\n <path\n d=\"m9.657 11.059 2.594-1.306-1.958 1.79-3.599 2.034.327-.954 2.636-1.564ZM1.456 5.753c.2.66.481 1.292.837 1.883a5.749 5.749 0 0 0 .837-1.674 22.903 22.903 0 0 0-.837-2.72 16.904 16.904 0 0 0-.837 2.511ZM3.13 8.18c.19.61.473 1.19.837 1.715a5.28 5.28 0 0 0 .837-1.673 6.795 6.795 0 0 0-.837-1.9A5.933 5.933 0 0 0 3.13 8.18Z\"\n />\n <path\n d=\"M1.674 9.92a8.577 8.577 0 0 0 2.276-.368A7.272 7.272 0 0 0 2.276 8.13 8.636 8.636 0 0 0 0 8.49a7.473 7.473 0 0 0 1.674 1.43ZM4.787 12.623a7.708 7.708 0 0 0 1.958-1.07 7.688 7.688 0 0 0-2.067-.838 7.97 7.97 0 0 0-1.958 1.072c.644.377 1.34.66 2.067.836ZM5.782 7.97a5.54 5.54 0 0 0-.469 1.934c.143.452 1.59 1.673 1.59 1.673a5.54 5.54 0 0 0 .47-1.941c-.152-.477-1.59-1.665-1.59-1.665Z\"\n />\n <path d=\"M6.343 11.059 3.75 9.753l1.958 1.79 3.599 2.034-.318-.954-2.645-1.564Z\" />\n </svg>\n }\n\n @if (icon === \"alert\") {\n <svg\n id=\"alert\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.634 8.5a1 1 0 0 1 1.73 0l7.5 13a1 1 0 0 1-.86 1.5h-15a1 1 0 0 1-.87-1.5l7.5-13Zm8.37 13.5-7.5-13-7.5 13h15Z\"\n clip-rule=\"evenodd\"\n />\n <circle cx=\"16.504\" cy=\"19.75\" r=\".75\" />\n <path d=\"M16.504 17.75a.75.75 0 0 1-.75-.75v-3a.75.75 0 0 1 1.5 0v3a.76.76 0 0 1-.75.75Z\" />\n </svg>\n }\n\n @if (icon === \"alert-fill\") {\n <svg\n id=\"alert-fill\"\n fill=\"none\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path fill=\"#fff\" fill-rule=\"evenodd\" d=\"m16.5 9.5 8 14h-16l8-14Z\" clip-rule=\"evenodd\" />\n <path\n fill=\"#B83C2E\"\n fill-rule=\"evenodd\"\n d=\"M15.634 9.5a1 1 0 0 1 1.73 0l7.5 13a1 1 0 0 1-.86 1.5h-15a1 1 0 0 1-.87-1.5l7.5-13Zm8.37 13.5-7.5-13-7.5 13h15Z\"\n clip-rule=\"evenodd\"\n />\n <circle fill=\"#B83C2E\" cx=\"16.504\" cy=\"20.75\" r=\".75\" />\n <path\n fill=\"#B83C2E\"\n d=\"M16.504 18.75a.75.75 0 0 1-.75-.75v-3a.75.75 0 0 1 1.5 0v3a.76.76 0 0 1-.75.75Z\"\n />\n </svg>\n }\n\n @if (icon === \"alert-small\") {\n <svg\n id=\"alert-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M7.186 1.47a.941.941 0 0 1 1.628 0l7.06 12.237a.942.942 0 0 1-.81 1.412H.945a.941.941 0 0 1-.819-1.412l7.06-12.236Zm7.879 12.708L8.005 1.941.945 14.178h14.12Z\"\n clip-rule=\"evenodd\"\n />\n <circle cx=\"8.005\" cy=\"12.06\" r=\".706\" />\n <path\n d=\"M8.005 10.178a.706.706 0 0 1-.706-.706V6.648a.706.706 0 1 1 1.412 0v2.824a.715.715 0 0 1-.706.706Z\"\n />\n </svg>\n }\n\n @if (icon === \"archive\") {\n <svg\n id=\"archive\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M25.429 11.83a.533.533 0 0 0-.533.533v10.304a1.6 1.6 0 0 1-1.6 1.6H9.78a1.6 1.6 0 0 1-1.6-1.6V12.363a.533.533 0 1 0-1.067 0v10.304a2.667 2.667 0 0 0 2.667 2.666h13.515a2.677 2.677 0 0 0 2.666-2.666V12.363a.533.533 0 0 0-.533-.534Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M8 6.4h17.066a2.667 2.667 0 0 1 2.667 2.667v1.962h-22.4V9.067A2.667 2.667 0 0 1 8 6.4ZM6.4 9.963h20.266v-.896a1.6 1.6 0 0 0-1.6-1.6H8a1.61 1.61 0 0 0-1.6 1.6v.896ZM20.267 17.248c0 .784-.636 1.419-1.42 1.419H14.23a1.419 1.419 0 0 1-1.43-1.419v-.352c0-.79.64-1.43 1.43-1.43h4.619a1.43 1.43 0 0 1 1.419 1.43v.352Zm-6.4-.352v.352a.363.363 0 0 0 .362.352h4.619a.352.352 0 0 0 .352-.352v-.352a.363.363 0 0 0-.352-.363h-4.62a.363.363 0 0 0-.362.363Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"arrow-big\") {\n <svg\n id=\"arrow-big\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.925 24.534a1.6 1.6 0 0 1-1.6-1.6v-9.93l-3.06 3.064a1.596 1.596 0 0 1-2.259.005 1.598 1.598 0 0 1-.011-2.258l5.792-5.87c.315-.322.75-.495 1.174-.478.39-.016.858.161 1.166.47l5.861 5.87a1.6 1.6 0 1 1-2.264 2.26l-3.2-3.202v10.07a1.6 1.6 0 0 1-1.6 1.6L36.267 16\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"arrow-dashed\") {\n <svg\n id=\"arrow-dashed\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M23.466 17.067H16v-2.134h7.466v2.134Zm-10.666 0H5.333v-2.134H12.8v2.134Z\" />\n <path d=\"m26.506 16-6.016 4.267v-8.534L26.506 16Z\" />\n </svg>\n }\n\n @if (icon === \"arrow-solid\") {\n <svg\n id=\"arrow-solid\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M25.868 15.182a1 1 0 0 1 0 1.636l-7.865 5.539a1 1 0 0 1-1.576-.818V10.461a1 1 0 0 1 1.576-.818l7.865 5.54Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M5.5 16a1 1 0 0 1 1-1H20a1 1 0 1 1 0 2H6.5a1 1 0 0 1-1-1Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"back\") {\n <svg\n id=\"back\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m18.684 14.403-.018-.003h-7.37l3.486-3.411c.2-.197.2-.513 0-.709a.518.518 0 0 0-.723 0l-4.25 4.16a.686.686 0 0 0 0 .986l4.25 4.16c.2.196.524.196.723 0 .2-.194.2-.512 0-.708l-3.486-3.411h7.37v.004c1.49-.037 2.667 1.19 2.667 2.662s-1.178 2.699-2.667 2.663v.004h-1.6a.533.533 0 0 0 0 1.067h1.6a3.737 3.737 0 0 0 3.733-3.734c0-2.057-1.654-3.756-3.715-3.73\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"brochure\") {\n <svg\n id=\"brochure\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M11 8.5a.5.5 0 0 0-.5.5v13.5a.5.5 0 0 0 .5.5h7v-2a1.5 1.5 0 0 1 1.5-1.5h2V9a.5.5 0 0 0-.5-.5H11Zm9.793 12H19.5a.5.5 0 0 0-.5.5v1.293l1.793-1.793ZM9.5 9A1.5 1.5 0 0 1 11 7.5h10A1.5 1.5 0 0 1 22.5 9v11.207L18.707 24H11a1.5 1.5 0 0 1-1.5-1.5V9Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M12 12a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"bullets\") {\n <svg\n id=\"bullets\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <circle cx=\"7\" cy=\"11\" r=\"1\" />\n <rect width=\"16\" height=\"2\" x=\"10\" y=\"10\" rx=\"1\" />\n <circle cx=\"7\" cy=\"16\" r=\"1\" />\n <rect width=\"16\" height=\"2\" x=\"10\" y=\"15\" rx=\"1\" />\n <circle cx=\"7\" cy=\"21\" r=\"1\" />\n <rect width=\"16\" height=\"2\" x=\"10\" y=\"20\" rx=\"1\" />\n </svg>\n }\n\n @if (icon === \"calendar\") {\n <svg\n id=\"calendar\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M22.394 24.533c.552 0 1.073-.813 1.073-1.365v-12.8c0-.553-.52-.768-1.073-.768H19.2V8.533h-1.066V9.6h-4.267V8.533H12.8V9.6H9.594c-.518 0-1.06.244-1.06.768v12.8c0 .553.507 1.365 1.06 1.365h12.8ZM9.6 10.667h3.2v1.066h1.067v-1.066h4.267v1.066H19.2v-1.066h3.2V12.8H9.6v-2.133Zm0 3.2h12.8v8.868c0 .276-.163.732-.44.732h-12c-.275 0-.36-.456-.36-.732v-8.868Zm4.267 5.333h-2.133v2.133h2.133V19.2Zm0-3.2h-2.133v2.133h2.133V16Zm3.2 3.2h-2.134v2.133h2.133V19.2Zm0-3.2h-2.134v2.133h2.133V16Zm3.2 3.2h-2.133v2.133h2.133V19.2Zm0-3.2h-2.133v2.133h2.133V16Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"change-log\") {\n <svg\n id=\"change-log\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16.5 15.717V11.2h-1v5.083l2.91 1.746.514-.858-2.424-1.454Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M9.066 16a6.934 6.934 0 1 1 3.736 6.152l.494-.95A5.862 5.862 0 1 0 10.138 16H9.066Z\"\n />\n <path d=\"M7.467 16h4.266L9.6 18.667 7.467 16Z\" />\n </svg>\n }\n\n @if (icon === \"check\") {\n <svg\n id=\"check\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M22.726 9.437a1.5 1.5 0 0 1 .371 2.088l-7.448 10.667a1.5 1.5 0 0 1-2.347.143l-4.285-4.779a1.5 1.5 0 0 1 2.234-2.003l3.022 3.37 6.364-9.115a1.5 1.5 0 0 1 2.089-.371Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"check-indeterminate-small\") {\n <svg\n id=\"check-indeterminate-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <rect width=\"12\" height=\"3\" x=\"2\" y=\"7\" rx=\"1.5\" />\n </svg>\n }\n\n @if (icon === \"check-small\") {\n <svg\n id=\"check-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13.572 3.18a1 1 0 0 1 .248 1.392l-5.713 8.182a1 1 0 0 1-1.564.095L3.256 9.184a1 1 0 1 1 1.489-1.335l2.444 2.726 4.991-7.147a1 1 0 0 1 1.393-.248Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"checkmark-circle\") {\n <svg\n id=\"checkmark-circle\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 25a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0 1c5.523 0 10-4.477 10-10S21.523 6 16 6 6 10.477 6 16s4.477 10 10 10Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M21.573 11.18a1 1 0 0 1 .247 1.393l-5.713 8.181a1 1 0 0 1-1.564.095l-3.287-3.665a1 1 0 0 1 1.489-1.335l2.444 2.726 4.991-7.147a1 1 0 0 1 1.393-.248Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"chevron\") {\n <svg\n id=\"chevron\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13.414 10.133a.61.61 0 0 0-.434 1.044L17.818 16l-4.838 4.822a.61.61 0 0 0 .434 1.045c.157 0 .315-.061.434-.18l5.102-5.085a.848.848 0 0 0 0-1.204l-5.102-5.085a.618.618 0 0 0-.434-.18\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"chevron-ring\") {\n <svg\n id=\"chevron-ring\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g fill-rule=\"evenodd\" clip-path=\"url(#a)\" clip-rule=\"evenodd\">\n <path\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n />\n <path\n d=\"M13.414 10.133a.61.61 0 0 0-.434 1.044L17.818 16l-4.838 4.822a.61.61 0 0 0 .434 1.045c.157 0 .315-.061.434-.18l5.102-5.085a.848.848 0 0 0 0-1.204l-5.102-5.085a.618.618 0 0 0-.434-.18Z\"\n />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"chevron-ring-fill\") {\n <svg\n id=\"chevron-ring-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path fill=\"#fff\" d=\"M29 16c0 7.18-5.82 13-13 13S3 23.18 3 16 8.82 3 16 3s13 5.82 13 13Z\" />\n <path\n fill=\"#484848\"\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill=\"#484848\"\n fill-rule=\"evenodd\"\n d=\"M13.414 10.133a.61.61 0 0 0-.434 1.044L17.818 16l-4.838 4.822a.61.61 0 0 0 .434 1.045c.157 0 .315-.061.434-.18l5.102-5.085a.848.848 0 0 0 0-1.204l-5.102-5.085a.618.618 0 0 0-.434-.18\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"clock\") {\n <svg\n id=\"clock\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M9.066 16a6.933 6.933 0 1 1 13.867 0 6.933 6.933 0 0 1-13.867 0Zm12.8 0A5.867 5.867 0 0 0 16 10.133 5.877 5.877 0 0 0 10.133 16a5.867 5.867 0 0 0 11.733 0Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M16.5 15.71V11.2h-1v5.09l2.908 1.659.496-.869-2.404-1.37Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M11.2 15.467h1.067v1.067H11.2zM19.733 15.467H20.8v1.067h-1.067zM15.467 19.733h1.067V20.8h-1.067z\"\n />\n </svg>\n }\n\n @if (icon === \"close\") {\n <svg\n id=\"close\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M20.255 20.932a.672.672 0 0 1-.47-.202l-8.533-8.534a.672.672 0 1 1 .95-.938l8.533 8.533a.672.672 0 0 1 0 .939.715.715 0 0 1-.48.202Z\"\n />\n <path\n d=\"M11.721 20.932a.714.714 0 0 0 .48-.202l8.533-8.534a.672.672 0 0 0-.949-.938l-8.533 8.533a.672.672 0 0 0 .469 1.141Z\"\n />\n </svg>\n }\n\n @if (icon === \"close-big\") {\n <svg\n id=\"close-big\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M25.566 6.403a.75.75 0 0 1 0 1.06L7.464 25.567a.75.75 0 0 1-1.06-1.061L24.504 6.403a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M6.403 6.403a.75.75 0 0 1 1.06 0l18.103 18.102a.75.75 0 0 1-1.06 1.06L6.402 7.465a.75.75 0 0 1 0-1.061Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"code\") {\n <svg\n id=\"code\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"m7.563 16.277 3.755 2.464c.15.107.256.203.32.299a.609.609 0 0 1 .096.341.66.66 0 0 1-.086.352.304.304 0 0 1-.256.128.524.524 0 0 1-.16-.032c-.053-.032-.138-.074-.245-.149l-4.064-2.71c-.01 0-.032-.01-.053-.031-.31-.203-.47-.427-.47-.662 0-.234.15-.458.46-.661.031-.021.052-.032.063-.043l4.064-2.666c.096-.054.17-.107.235-.128.064-.022.117-.043.17-.043.107 0 .192.043.246.128a.53.53 0 0 1 .096.33.645.645 0 0 1-.096.353.971.971 0 0 1-.32.298l-3.755 2.432ZM14.508 20.235c-.107.245-.214.416-.31.501a.484.484 0 0 1-.341.128.484.484 0 0 1-.352-.15.468.468 0 0 1-.15-.362c0-.043.011-.085.022-.139.01-.053.032-.106.053-.16l4.555-7.669c.117-.256.224-.427.32-.512a.488.488 0 0 1 .33-.139c.15 0 .267.054.374.15.107.096.15.213.15.352 0 .042-.011.085-.022.128a.537.537 0 0 1-.053.138l-4.576 7.734ZM24.875 16.277l-3.744-2.432a1.164 1.164 0 0 1-.33-.298.609.609 0 0 1-.097-.342c0-.138.032-.256.085-.33a.286.286 0 0 1 .246-.128c.053 0 .106.01.17.042.064.022.14.075.235.139l4.064 2.667c.01.01.032.021.064.042.31.192.459.416.459.662 0 .234-.15.458-.448.661a.608.608 0 0 1-.075.043l-4.064 2.709-.021.01c-.182.107-.31.171-.384.171a.286.286 0 0 1-.246-.128c-.064-.085-.085-.202-.085-.352 0-.128.032-.234.096-.341.064-.096.17-.203.33-.299l3.745-2.496Z\"\n />\n </svg>\n }\n\n @if (icon === \"decrease-indent\") {\n <svg\n id=\"decrease-indent\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M9.066 16.533h9.6v-1.067h-9.6v1.067ZM9.066 14.4h9.6v-1.067h-9.6V14.4ZM9.066 12.267h13.867V11.2H9.066v1.067ZM9.066 18.667h9.6V17.6h-9.6v1.067ZM9.066 20.8h13.867v-1.067H9.066V20.8ZM19.733 16l3.2 2.133v-4.266L19.733 16Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"delete\") {\n <svg\n id=\"delete\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M9.067 25.067v-16h13.867v16H9.067ZM10.134 24h11.733V10.133H10.134V24Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M15.467 12.267h1.067V20.8h-1.067zM12.267 14.4h1.067v6.4h-1.067zM18.667 14.4h1.067v6.4h-1.067zM6.934 9.067h18.133v1.067H6.934zM13.334 6.933v2.134h1.067V8h3.2v1.067h1.066V6.933h-5.333Z\"\n />\n </svg>\n }\n\n @if (icon === \"dot\") {\n <svg\n id=\"dot\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <circle cx=\"16\" cy=\"16\" r=\"4.267\" />\n </svg>\n }\n\n @if (icon === \"edit\") {\n <svg\n id=\"edit\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"m7.797 24.235 4.64-1.248-3.381-3.382-1.26 4.63ZM9.637 19.04l8.794-8.795 3.387 3.386-8.795 8.795-3.386-3.387ZM22.517 12.907l1.494-1.483a.768.768 0 0 0 0-1.067L21.706 8a.757.757 0 0 0-1.066 0l-1.494 1.483 3.371 3.424Z\"\n />\n </svg>\n }\n\n @if (icon === \"editor\") {\n <svg\n id=\"editor\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m11.246 16.85 4.258-9.58a.532.532 0 0 1 .713-.29c.133.058.23.165.279.29l4.258 9.58a.533.533 0 0 1-.975.433L18.498 14.4h-4.996l-1.281 2.883a.533.533 0 1 1-.975-.433Zm2.73-3.517h4.048L16 8.78l-2.023 4.553ZM13.679 24a1.004 1.004 0 1 1-2.008 0 1.004 1.004 0 0 1 2.008 0Zm5.521-1.004a1.004 1.004 0 1 0 0 2.008 1.004 1.004 0 0 0 0-2.008ZM14.934 24a1.004 1.004 0 1 1 2.008 0 1.004 1.004 0 0 1-2.008 0Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"exclamation-mark-circle\") {\n <svg\n id=\"exclamation-mark-circle\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 25a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0 1c5.523 0 10-4.477 10-10S21.523 6 16 6 6 10.477 6 16s4.477 10 10 10Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M16 18.5c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1Zm0-1.5c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1Z\"\n />\n </svg>\n }\n\n @if (icon === \"exclamation-mark-circle-fill\") {\n <svg\n id=\"exclamation-mark-circle-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <circle cx=\"16\" cy=\"15\" r=\"10\" fill=\"#B83C2E\" />\n <path\n fill=\"#fff\"\n d=\"M16 18.5c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1Zm0-1.5c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1Z\"\n />\n </svg>\n }\n\n @if (icon === \"exclamation-mark-circle-small\") {\n <svg\n id=\"exclamation-mark-circle-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M8 10.5c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1M8 9c-.55 0-1-.45-1-1V4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1\"\n />\n <path d=\"M15 8A7 7 0 1 0 1 8a7 7 0 0 0 14 0m1 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0\" />\n </svg>\n }\n\n @if (icon === \"eye\") {\n <svg\n id=\"eye\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.75 10.23c1.451-.01 2.894.21 4.274.654l.14.045.094.114a5.737 5.737 0 1 1-8.87 0l.387.317-.151-.476a13.863 13.863 0 0 1 4.127-.653Zm-3.68 1.562a4.737 4.737 0 1 0 7.505-.001 12.596 12.596 0 0 0-3.817-.56h-.001c-1.25.007-2.492.196-3.686.56Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M15.807 12.01a2.667 2.667 0 1 0 2.667 2.667 2.656 2.656 0 0 0-2.667-2.666Z\" />\n <path\n fill-rule=\"evenodd\"\n d=\"M6.307 15.915Zm.143-.235a7.3 7.3 0 0 1 .34-.478 9.683 9.683 0 0 1 1.62-1.638c1.49-1.19 3.854-2.397 7.344-2.397s5.854 1.206 7.344 2.397a9.686 9.686 0 0 1 1.62 1.638c.148.192.26.356.34.478a7.37 7.37 0 0 1-.341.478 9.715 9.715 0 0 1-1.623 1.638c-1.491 1.191-3.856 2.397-7.34 2.397-3.485 0-5.849-1.206-7.34-2.397a9.717 9.717 0 0 1-1.623-1.638 7.327 7.327 0 0 1-.34-.478Zm19.192 0c.442-.234.442-.234.441-.235v-.002l-.003-.005-.008-.014a3.061 3.061 0 0 0-.136-.227 8.103 8.103 0 0 0-.426-.605 10.678 10.678 0 0 0-1.788-1.81c-1.646-1.315-4.226-2.615-7.968-2.615s-6.322 1.3-7.968 2.616a10.678 10.678 0 0 0-1.788 1.809 8.111 8.111 0 0 0-.533.781 3.033 3.033 0 0 0-.029.05l-.008.015-.002.005-.001.002.44.235-.44-.234-.125.235.124.234.442-.235-.442.235.001.001.001.002.003.004.008.015a3.798 3.798 0 0 0 .135.226c.095.15.236.358.428.606.383.496.969 1.153 1.79 1.808 1.647 1.316 4.227 2.616 7.964 2.616s6.316-1.3 7.964-2.616a10.719 10.719 0 0 0 1.79-1.808 8.113 8.113 0 0 0 .535-.782l.028-.05.008-.015.003-.004v-.002s.001-.001-.44-.236Zm0 0 .441.235.125-.234-.125-.236-.441.235Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"eye-blind\") {\n <svg\n id=\"eye-blind\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M15.8 11.2c-2.1 0-3.8.4-5.1 1-.9 1.8-.7 4 .4 5.7l.7-.7c-1-1.6-1-3.8.3-5.4 1.2-.4 2.4-.6 3.7-.6 0 .1.1.1.1.1.6 0 1.2 0 1.8.1l.1-.1c-.7-.1-1.4-.1-2-.1ZM20.5 12l-.5.5c1 1.9.6 4.2-1 5.7-.9.9-2.1 1.3-3.2 1.3-.8 0-1.5-.2-2.2-.5l-.7.7c.3.2.6.3.9.4.6.1 1.2.1 1.9.1.8 0 1.5-.1 2.2-.2.7-.3 1.3-.6 1.8-1.2 1.8-1.7 2.3-4.4 1.3-6.6-.1 0-.3-.1-.5-.2Z\"\n />\n <path\n d=\"M15.8 12c-1.5 0-2.7 1.2-2.7 2.7 0 .4.1.7.2 1l3.5-3.5c-.3-.1-.6-.2-1-.2ZM18.4 14.1l-3.2 3.2c.2 0 .4.1.6.1 1.5 0 2.7-1.2 2.7-2.7 0-.2 0-.4-.1-.6Z\"\n />\n <path\n d=\"M15.8 10.2c-3.7 0-6.3 1.3-8 2.6-.8.6-1.4 1.3-1.8 1.8-.2.2-.3.5-.4.6l-.1.2v.1l.4.2-.4.2v.1l.1.2c.1.1.2.4.4.6.4.5 1 1.2 1.8 1.8.5.4 1 .7 1.6 1.1l.7-.7c-.7-.3-1.2-.7-1.7-1.1-.7-.6-1.3-1.2-1.6-1.6-.1-.2-.3-.4-.3-.5.1-.1.2-.3.3-.5.3-.4.9-1 1.6-1.6.6-.5 1.3-1 2.2-1.4 1.3-.6 3-1 5.1-1 .7 0 1.3 0 1.9.1l.9-.9c-.8-.2-1.7-.3-2.7-.3ZM26.1 15.4l-.4.2.4.2.1-.2-.1-.2Zm-4.9-4.1-.7.7c.2.1.4.2.6.2.8.4 1.5.8 2.1 1.3.7.6 1.3 1.2 1.6 1.6.1.2.3.4.3.5-.1.1-.2.3-.3.5-.3.4-.9 1-1.6 1.6-1.2.9-2.8 1.9-5.2 2.2-.7.1-1.4.2-2.2.2-.7 0-1.3 0-1.9-.1-.4-.1-.8-.1-1.2-.2l-.8.8c1.1.3 2.4.5 3.9.5 3.7 0 6.3-1.3 8-2.6.8-.7 1.4-1.3 1.8-1.8.2-.2.3-.5.4-.6v-.2l-.4-.2.4-.2v-.1l-.1-.2c-.1-.1-.2-.4-.4-.6-.4-.5-1-1.2-1.8-1.8-.6-.6-1.5-1.1-2.5-1.5Z\"\n />\n <path d=\"m5.9 15.7-.4-.2-.1.2.1.2.4-.2Z\" />\n </svg>\n }\n\n @if (icon === \"fail\") {\n <svg\n id=\"fail\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M12.11 9.934a1.545 1.545 0 1 0-2.184 2.168l10.06 10.053c.284.289.67.454 1.075.461h.033c.4-.01.783-.171 1.07-.451l.007-.007.007-.007c.586-.6.586-1.557 0-2.157L12.11 9.934Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M20.093 9.724a1.553 1.553 0 0 1 2.194 2.17l-.028.035L12.02 22.162c-.289.282-.673.444-1.077.454h-.033a1.553 1.553 0 0 1-1.084-2.637l.006-.006L20.061 9.75l.032-.026Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"hourglass\") {\n <svg\n id=\"hourglass\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m16.896 16 3.904-2.848V9.6h1.067V8.533H10.134V9.6H11.2v3.552L15.095 16 11.2 18.848V22.4h-1.067v1.067h11.733V22.4H20.8v-3.552L16.896 16Zm-4.628-3.392L16 15.338l3.733-2.73V9.6h-7.466v3.008Zm7.466 9.792v-3.008l-3.733-2.73-3.733 2.73V22.4h7.466Zm-6.4-10.336v-.33h5.333v.33L16 14.016l-2.666-1.952Zm5.333 8.203h-5.333v1.066h5.333v-1.066Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"increase-indent\") {\n <svg\n id=\"increase-indent\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13.333 16.533h9.6v-1.067h-9.6v1.067ZM13.333 18.667h9.6V17.6h-9.6v1.067ZM9.066 20.8h13.867v-1.067H9.066V20.8ZM13.333 14.4h9.6v-1.067h-9.6V14.4ZM9.066 12.267h13.867V11.2H9.066v1.067ZM9.066 18.133l3.2-2.133-3.2-2.133v4.266Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"info\") {\n <svg\n id=\"info\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <circle cx=\"16\" cy=\"11.5\" r=\"1.5\" />\n <path d=\"M17 20.5v-6h-3v1h1v5h-1v1h4v-1h-1Z\" />\n </svg>\n }\n\n @if (icon === \"info-circle\") {\n <svg\n id=\"info-circle\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 25a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0 1c5.523 0 10-4.477 10-10S21.523 6 16 6 6 10.477 6 16s4.477 10 10 10Z\"\n clip-rule=\"evenodd\"\n />\n <circle cx=\"16\" cy=\"11.5\" r=\"1.5\" />\n <path d=\"M17 20.5v-6h-3v1h1v5h-1v1h4v-1h-1Z\" />\n </svg>\n }\n\n @if (icon === \"info-circle-fill\") {\n <svg\n id=\"info-circle-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path\n fill=\"#fff\"\n d=\"M26 16c0 5.523-4.477 10-10 10S6 21.523 6 16 10.477 6 16 6s10 4.477 10 10Z\"\n />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M16 25a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0 1c5.523 0 10-4.477 10-10S21.523 6 16 6 6 10.477 6 16s4.477 10 10 10Z\"\n clip-rule=\"evenodd\"\n />\n <circle cx=\"16\" cy=\"11.5\" r=\"1.5\" fill=\"#1076DB\" />\n <path fill=\"#1076DB\" d=\"M17 20.5v-6h-3v1h1v5h-1v1h4v-1h-1Z\" />\n </svg>\n }\n\n @if (icon === \"info-circle-small\") {\n <svg\n id=\"info-circle-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path d=\"M15 8A7 7 0 1 0 1 8a7 7 0 0 0 14 0m1 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0\" />\n <path d=\"M8 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3M9 12V7H6v1h1v4H6v1h4v-1z\" />\n </svg>\n }\n\n @if (icon === \"junction\") {\n <svg\n id=\"junction\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M19.2 8.565h-8.533L6.4 11.232l4.267 2.667H19.2V8.565ZM22.294 14.965h-8.533V20.3h8.533l4.267-2.667-4.267-2.667ZM16.427 21.365a.528.528 0 0 0-.533.534v3.2c0 .298.234.533.533.533a.528.528 0 0 0 .533-.533v-3.2a.528.528 0 0 0-.533-.534ZM16.427 7.477a.528.528 0 0 0 .533-.533V5.867a.528.528 0 0 0-.533-.534.528.528 0 0 0-.533.534v1.077c0 .299.234.533.533.533Z\"\n />\n </svg>\n }\n\n @if (icon === \"link\") {\n <svg\n id=\"link\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M20.707 11.121a1 1 0 0 0-1.414 0l-.354.354a1 1 0 0 1-1.414-1.414l.354-.354a3 3 0 0 1 4.242 0l.707.707a3 3 0 0 1 0 4.243l-4.242 4.242a2.5 2.5 0 0 1-3.536 0 1 1 0 1 1 1.415-1.414.5.5 0 0 0 .707 0l4.242-4.242a1 1 0 0 0 0-1.415l-.707-.707Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M11.868 21.374a1 1 0 0 0 1.414 0l.354-.353a1 1 0 0 1 1.414 1.414l-.354.354a3 3 0 0 1-4.242 0l-.707-.708a3 3 0 0 1 0-4.242l3.889-3.89a3 3 0 0 1 4.242 0l.354.354a1 1 0 1 1-1.414 1.414l-.354-.353a1 1 0 0 0-1.414 0l-3.889 3.89a1 1 0 0 0 0 1.413l.707.707Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"list-add\") {\n <svg\n id=\"list-add\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M12.5 6.5a.5.5 0 0 1 .5.5v2h2.5V7a.5.5 0 0 1 1 0v2H19V7a.5.5 0 0 1 1 0v2h1a1 1 0 0 1 1 1v1a4.978 4.978 0 0 0-3 1h-6.5a.5.5 0 0 0 0 1H18a4.977 4.977 0 0 0-.9 2h-4.6a.5.5 0 0 0 0 1H17a5 5 0 0 0 5 5v1a1 1 0 0 1-1 1H11a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1h1V7a.5.5 0 0 1 .5-.5Zm0 11.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22 12.5a.5.5 0 0 1 .5.5v2.5H25a.5.5 0 0 1 0 1h-2.5V19a.5.5 0 0 1-1 0v-2.5H19a.5.5 0 0 1 0-1h2.5V13a.5.5 0 0 1 .5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"list-minus\") {\n <svg\n id=\"list-minus\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M12.5 6.5a.5.5 0 0 1 .5.5v2h2.5V7a.5.5 0 0 1 1 0v2H19V7a.5.5 0 0 1 1 0v2h1a1 1 0 0 1 1 1v1a4.978 4.978 0 0 0-3 1h-6.5a.5.5 0 0 0 0 1H18a4.977 4.977 0 0 0-.9 2h-4.6a.5.5 0 0 0 0 1H17a5 5 0 0 0 5 5v1a1 1 0 0 1-1 1H11a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1h1V7a.5.5 0 0 1 .5-.5Zm0 11.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M18.5 16a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"lock\") {\n <svg\n id=\"lock\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M20.8 13.43h-1.066v-.534a3.733 3.733 0 1 0-7.467 0v.533h-1.066c-.59 0-1.067.478-1.067 1.067v6.933a1.6 1.6 0 0 0 1.6 1.6h8.533a1.6 1.6 0 0 0 1.6-1.6v-6.933c0-.59-.477-1.067-1.067-1.067Zm-7.466-.534a2.667 2.667 0 1 1 5.333 0v.533h-5.333v-.533Zm6.933 9.067a.533.533 0 0 0 .533-.534v-6.933h-9.6v6.933a.544.544 0 0 0 .534.534h8.533Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M17.6 17.163a1.6 1.6 0 1 0-2.4 1.376v1.557a.8.8 0 1 0 1.6 0v-1.557a1.59 1.59 0 0 0 .8-1.376Z\"\n />\n </svg>\n }\n\n @if (icon === \"lock-open\") {\n <svg\n id=\"lock-open\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M12.267 12.279v-.06 3.344H20.8c.59 0 1.067.477 1.067 1.066v6.934a1.6 1.6 0 0 1-1.6 1.6h-8.533a1.6 1.6 0 0 1-1.6-1.6v-6.934c0-.589.477-1.066 1.066-1.066h1.067v-3.284Zm8 11.817a.533.533 0 0 0 .533-.533v-6.934h-9.6v6.934a.544.544 0 0 0 .534.533h8.533Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M17.6 19.296a1.6 1.6 0 1 0-2.4 1.376v1.557a.8.8 0 1 0 1.6 0v-1.557a1.59 1.59 0 0 0 .8-1.376Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M12.268 16.106V11.66C12.268 9.708 13.976 8 16 8c2.025 0 3.733 1.708 3.733 3.66v.073h-1.067v-.073c0-1.363-1.23-2.593-2.666-2.593s-2.667 1.23-2.667 2.593v4.446h-1.066Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"magic-wand\") {\n <svg\n id=\"magic-wand\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m14.264 16.686-7.643 7.717a.722.722 0 0 0 0 1.04c.147.148.294.148.588.148s.44 0 .588-.149l7.643-7.717a.722.722 0 0 0 0-1.039c-.294-.296-.882-.296-1.176 0\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M19.703 13.125c.147 0 .441 0 .588-.149l2.94-2.968a.722.722 0 0 0 0-1.039.706.706 0 0 0-1.03 0l-2.939 2.968c-.294.149-.294.594-.147.89.147.15.441.298.588.298M20.29 16.686a.706.706 0 0 0-1.028 0 .722.722 0 0 0 0 1.04l2.94 2.967c.146.149.293.149.587.149s.441 0 .588-.149a.722.722 0 0 0 0-1.039l-3.087-2.968ZM12.5 8.82a.706.706 0 0 0-1.028 0 .722.722 0 0 0 0 1.04l2.94 2.968c.146.148.293.148.587.148s.441 0 .588-.148a.722.722 0 0 0 0-1.039L12.5 8.821ZM17.351 17.577c-.44 0-.735.296-.735.742v4.007c0 .445.294.742.735.742s.735-.297.735-.742v-4.007c0-.445-.44-.742-.735-.742\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M17.351 6.446c-.44 0-.735.297-.735.742v4.007c0 .445.294.742.735.742s.735-.296.735-.742V7.188c0-.445-.44-.742-.735-.742\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M9.854 15.5h3.969c.44 0 .735-.298.735-.743s-.441-.742-.735-.742H9.854c-.44 0-.735.297-.735.742s.294.742.735.742\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M24.847 14.015h-3.969c-.44 0-.734.297-.734.742s.294.742.735.742h3.968c.441 0 .735-.297.735-.742s-.441-.742-.735-.742\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"mail\") {\n <svg\n id=\"mail\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M24 10H8l8 7 8-7Z\" />\n <path d=\"m16 18.5-8-7V21a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-9.5l-8 7Z\" />\n </svg>\n }\n\n @if (icon === \"menu\") {\n <svg\n id=\"menu\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M28.8 9.067H3.2V5.333h25.6v3.734Zm0 4.8H3.2V17.6h25.6v-3.733Zm0 8.533H3.2v3.733h25.6V22.4Z\"\n />\n </svg>\n }\n\n @if (icon === \"message\") {\n <svg\n id=\"message\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M11 25.5V22h3.5L11 25.5Z\" />\n <path\n fill-rule=\"evenodd\"\n d=\"M7 11a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2v-9Zm3 5.75a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1-.75-.75Zm.75-4.75a.75.75 0 0 0 0 1.5h10.5a.75.75 0 0 0 0-1.5h-10.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"minus\") {\n <svg\n id=\"minus\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M10 15h12v1H10z\" />\n </svg>\n }\n\n @if (icon === \"minus-ring-fill\") {\n <svg\n id=\"minus-ring-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path fill=\"#fff\" d=\"M29 16c0 7.18-5.82 13-13 13S3 23.18 3 16 8.82 3 16 3s13 5.82 13 13Z\" />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path fill=\"#1076DB\" d=\"M10 15h12v1H10z\" />\n </svg>\n }\n\n @if (icon === \"new-tab\") {\n <svg\n id=\"new-tab\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M17.571 8H23.464c.296 0 .536.24.536.536V14.429h-1.071v-4.6l-7.657 7.657-.38.379-.757-.758.38-.379 7.656-7.657h-4.6V8ZM9 10.143h7.5v1.071h-6.429V21.93h10.715V15.5h1.071V23H9V10.143Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"notebook\") {\n <svg\n id=\"notebook\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13 7a.5.5 0 0 0-1 0v2h-1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V10a1 1 0 0 0-1-1h-1V7a.5.5 0 0 0-1 0v2h-2.5V7a.5.5 0 0 0-1 0v2H13V7Zm-1 11.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5Zm.5-3.5a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7Zm-.5-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"notification\") {\n <svg\n id=\"notification\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 24a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm0 1a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z\"\n clip-rule=\"evenodd\"\n />\n <g clip-path=\"url(#a)\">\n <circle cx=\"16\" cy=\"11.5\" r=\"1.5\" />\n <path d=\"M17 20.5v-6h-3v1h1v5h-1v1h4v-1h-1Z\" />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M8 8h16v16H8z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"numbering\") {\n <svg\n id=\"numbering\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M8.897 8v4.732H7.812V9.078l-1.015.623-.483-.735L7.91 8h.987ZM7.705 13.902c.34 0 .632.06.875.182.247.121.434.285.56.49.13.205.196.432.196.679 0 .252-.051.492-.154.721-.103.229-.285.492-.546.791-.261.294-.642.667-1.14 1.12h1.938l-.112.847h-3.08v-.784c.574-.565.992-.99 1.253-1.274.261-.29.443-.53.546-.721.107-.191.161-.387.161-.588 0-.191-.056-.34-.168-.448a.582.582 0 0 0-.441-.168.845.845 0 0 0-.448.119 1.61 1.61 0 0 0-.413.371l-.65-.518a2.15 2.15 0 0 1 .699-.602c.275-.145.583-.217.924-.217ZM7.701 19.902c.331 0 .614.054.847.161.238.107.418.252.539.434.126.182.189.385.189.609 0 .285-.082.523-.245.714-.159.191-.385.329-.679.413.317.033.576.147.777.343.2.191.301.464.301.819 0 .266-.07.511-.21.735-.14.22-.348.394-.623.525-.27.13-.595.196-.973.196-.69 0-1.232-.238-1.624-.714l.595-.581c.159.159.313.275.462.35.154.07.322.105.504.105.22 0 .397-.06.532-.182.135-.121.203-.29.203-.504 0-.243-.065-.415-.196-.518-.126-.107-.317-.161-.574-.161h-.392l.119-.763h.273c.21 0 .376-.054.497-.161.121-.107.182-.259.182-.455a.49.49 0 0 0-.168-.399c-.112-.098-.266-.147-.462-.147-.331 0-.64.138-.924.413l-.546-.595a2.24 2.24 0 0 1 1.596-.637Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M12.347 10.232a.5.5 0 0 1 .5-.5h12.5a.5.5 0 0 1 0 1h-12.5a.5.5 0 0 1-.5-.5ZM12.347 16.732a.5.5 0 0 1 .5-.5h12.5a.5.5 0 0 1 0 1h-12.5a.5.5 0 0 1-.5-.5ZM12.347 22.732a.5.5 0 0 1 .5-.5h12.5a.5.5 0 0 1 0 1h-12.5a.5.5 0 0 1-.5-.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"pdf\") {\n <svg\n id=\"pdf\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M7.69 12.363c1.857 0 2.987.757 2.987 2.346 0 1.696-1.226 2.454-2.89 2.454h-.864v2.57H5.515v-7.37H7.69Zm-.01 3.744c.939 0 1.525-.32 1.525-1.398 0-.928-.565-1.312-1.525-1.312h-.757v2.71h.757Zm5.952-3.744c1.941 0 3.776.597 3.776 3.648 0 2.986-1.824 3.722-3.584 3.722h-2.006v-7.37h1.814Zm-.405 1.056v5.258h.693c1.099 0 2.005-.49 2.005-2.666 0-2.219-.96-2.592-2.048-2.592h-.65Zm9.525-.011h-2.613v2.208h2.272v1.045h-2.272v3.072H18.73v-7.37h4.17l-.149 1.045Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M7.5 6A1.5 1.5 0 0 1 9 4.5h15A1.5 1.5 0 0 1 25.5 6v16.207L20.707 27H9a1.5 1.5 0 0 1-1.5-1.5V21h1v4.5a.5.5 0 0 0 .5.5h11v-3a1.5 1.5 0 0 1 1.5-1.5h3V6a.5.5 0 0 0-.5-.5H9a.5.5 0 0 0-.5.5v5h-1V6Zm16.293 16.5H21.5a.5.5 0 0 0-.5.5v2.293l2.793-2.793Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"paperclip\") {\n <svg\n id=\"paperclip\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M11 8.594a3.594 3.594 0 1 1 7.188 0V21.53a2.156 2.156 0 0 1-4.313 0V11.47a.719.719 0 1 1 1.438 0V21.53a.719.719 0 1 0 1.437 0V8.594a2.157 2.157 0 0 0-4.312 0V21.53a3.593 3.593 0 1 0 7.187 0V11.47a.719.719 0 1 1 1.438 0V21.53a5.032 5.032 0 0 1-10.063 0z\"\n />\n </svg>\n }\n\n @if (icon === \"people\") {\n <svg\n id=\"people\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"34\"\n height=\"33\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M25 17.197a6 6 0 1 0-6.367-10.164A6 6 0 0 1 21 18.197V19h2a4 4 0 0 1 4 4v1h4v-2a4 4 0 0 0-4-4h-2v-.803ZM14.353 19H15v-.465c-.231.134-.448.29-.646.465Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22 14c0 2.22-1.207 4.16-3 5.197V20h2a4 4 0 0 1 4 4v2H7v-2a4 4 0 0 1 4-4h2v-.803A6 6 0 1 1 22 14Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"person\") {\n <svg\n id=\"person\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M22 13c0 2.22-1.207 4.16-3 5.197V19h2a4 4 0 0 1 4 4v2H7v-2a4 4 0 0 1 4-4h2v-.803A6 6 0 1 1 22 13Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"person-small\") {\n <svg\n id=\"person-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M13 6a4.993 4.993 0 0 1-2 4v1h1a4 4 0 0 1 4 4v1H0v-1a4 4 0 0 1 4-4h1v-1a5 5 0 1 1 8-4Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"picker\") {\n <svg\n id=\"picker\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16.794 14.851a3.396 3.396 0 0 1 3.392-3.392 3.395 3.395 0 0 1 3.393 3.392 3.396 3.396 0 0 1-3.393 3.392 3.396 3.396 0 0 1-3.392-3.392m10.408 6.512-3.01-3.122a5.22 5.22 0 0 0 1.247-3.39A5.258 5.258 0 0 0 20.186 9.6a5.257 5.257 0 0 0-5.252 5.251 5.258 5.258 0 0 0 5.252 5.25c.932 0 1.807-.246 2.565-.673l3.093 3.208c.361.372.96.383 1.334.023a.915.915 0 0 0 .024-1.296\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M3.2 17.067h8.533V16H3.2v1.067ZM3.2 12.8h9.6v-1.067H3.2V12.8ZM3.2 8.533h10.667V7.467H3.2v1.066ZM3.2 21.333h9.6v-1.066H3.2v1.066ZM3.2 25.6h8.533v-1.067H3.2V25.6Z\"\n />\n </svg>\n }\n\n @if (icon === \"pin-small\") {\n <svg\n id=\"pin-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M8.34 4C6.5 4 5 5.52 5 7.39c0 .6.16 1.23.43 1.67l.02.04c.07.12.38.52.8 1 .86.99 2.11 2.33 2.11 2.33s1.24-1.36 2.09-2.34c.4-.47.7-.86.77-.97l.04-.07c.28-.44.43-1.07.43-1.67 0-1.86-1.5-3.38-3.35-3.38Zm0 5.24c-1.04 0-1.89-.85-1.89-1.89s.85-1.9 1.89-1.9 1.9.85 1.9 1.89-.85 1.9-1.9 1.9Z\"\n />\n </svg>\n }\n\n @if (icon === \"place\") {\n <svg\n id=\"place\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M21.867 8.533c0 .295-.219.534-.489.534H10.623c-.27 0-.49-.24-.49-.534 0-.294.22-.533.49-.533h10.755c.27 0 .49.239.49.533M15.623 11.89l-5.333 5.333a.534.534 0 0 0 .754.754l4.423-4.422v9.912a.533.533 0 1 0 1.067 0v-9.912l4.422 4.422a.534.534 0 0 0 .755-.754l-5.334-5.333a.534.534 0 0 0-.754 0\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"place-ring-fill\") {\n <svg\n id=\"place-ring-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path fill=\"#fff\" d=\"M29 16c0 7.18-5.82 13-13 13S3 23.18 3 16 8.82 3 16 3s13 5.82 13 13Z\" />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M23.467 21.733c-.294 0-.533-.219-.533-.489V10.49c0-.27.238-.489.533-.489.294 0 .533.219.533.489v10.755c0 .27-.239.49-.533.49ZM20.11 15.49l-5.333-5.334a.534.534 0 0 0-.754.754l4.422 4.424H8.532a.533.533 0 1 0 0 1.066h9.913l-4.422 4.423a.534.534 0 0 0 .754.754l5.333-5.334a.534.534 0 0 0 0-.754Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"plus\") {\n <svg\n id=\"plus\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path d=\"M16 9h1v13h-1z\" />\n <path d=\"M10 15h13v1H10z\" />\n </svg>\n }\n\n @if (icon === \"print\") {\n <svg\n id=\"print\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <mask\n id=\"a\"\n width=\"20\"\n height=\"20\"\n x=\"6\"\n y=\"6\"\n maskUnits=\"userSpaceOnUse\"\n style=\"mask-type: luminance\"\n >\n <path fill-rule=\"evenodd\" d=\"M6.4 6.933h19.2v18.134H6.4V6.933Z\" clip-rule=\"evenodd\" />\n </mask>\n <path\n fill-rule=\"evenodd\"\n d=\"m21.654 24-11.414.107v-6.4c0-.214.107-.32.214-.32H21.44c.107 0 .214.106.214.32V24ZM10.24 8h11.414v1.813H10.24V8Zm12.587 16.107V22.08c.107 0 .213.107.32.107h1.173c.747 0 1.28-.64 1.28-1.387v-8.32c0-1.387-1.066-2.56-2.346-2.56h-.427V7.893c0-.533-.533-.96-1.067-.96H10.24c-.64 0-1.066.427-1.066.96V9.92h-.427c-1.28 0-2.347 1.173-2.347 2.56v8.213c0 .747.64 1.387 1.28 1.387h1.387v2.027c0 .533.533.96 1.067.96h11.413c.747 0 1.28-.427 1.28-.96Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M11.84 20.053h8.32v-1.066h-8.32v1.066ZM11.84 22.827h8.32V21.76h-8.32v1.067Z\" />\n </svg>\n }\n\n @if (icon === \"question-mark\") {\n <svg\n id=\"question-mark\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M15.992 7c.96 0 1.784.176 2.472.528.704.336 1.232.792 1.584 1.368.352.576.528 1.192.528 1.848 0 .624-.112 1.16-.336 1.608a3.336 3.336 0 0 1-.768 1.056c-.304.272-.712.592-1.224.96-.672.464-1.176.896-1.512 1.296-.336.384-.504.904-.504 1.56v1.08h-2.184v-1.2c0-.688.112-1.272.336-1.752.24-.48.52-.864.84-1.152.32-.288.744-.616 1.272-.984.608-.4 1.056-.76 1.344-1.08.288-.32.432-.736.432-1.248 0-.656-.216-1.16-.648-1.512-.432-.368-1.024-.552-1.776-.552-1.28 0-2.392.576-3.336 1.728L11 9.376C12.344 7.792 14.008 7 15.992 7ZM15.2 20.776c.48 0 .88.16 1.2.48.32.32.48.712.48 1.176 0 .464-.16.864-.48 1.2-.32.32-.72.48-1.2.48a1.6 1.6 0 0 1-1.176-.48 1.68 1.68 0 0 1-.48-1.2c0-.464.16-.856.48-1.176a1.6 1.6 0 0 1 1.176-.48Z\"\n />\n </svg>\n }\n\n @if (icon === \"question-mark-small\") {\n <svg\n id=\"question-mark-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M8.094 3c.616 0 1.139.103 1.568.308.439.205.77.48.994.826.224.345.336.723.336 1.134 0 .373-.065.695-.196.966-.13.261-.29.476-.476.644-.187.168-.43.355-.728.56-.383.252-.667.485-.854.7-.187.215-.28.5-.28.854v.476H6.792v-.546c0-.42.065-.775.196-1.064.14-.299.303-.532.49-.7.187-.177.43-.369.728-.574.317-.215.546-.401.686-.56a.862.862 0 0 0 .224-.616c0-.308-.107-.546-.322-.714-.205-.177-.49-.266-.854-.266-.663 0-1.255.308-1.778.924L5 4.442C5.812 3.481 6.843 3 8.094 3Zm-.448 7.826c.317 0 .588.112.812.336.224.215.336.48.336.798 0 .308-.112.574-.336.798a1.106 1.106 0 0 1-.812.336 1.09 1.09 0 0 1-.798-.336 1.114 1.114 0 0 1-.322-.798c0-.317.107-.583.322-.798a1.06 1.06 0 0 1 .798-.336Z\"\n />\n </svg>\n }\n\n @if (icon === \"required\") {\n <svg\n id=\"required\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.745 9a.75.75 0 0 1 .75.75v4.676l3.87-2.32a.75.75 0 0 1 .77 1.287l-3.931 2.357 3.932 2.357a.75.75 0 1 1-.771 1.286l-3.87-2.319v4.676a.75.75 0 0 1-1.5 0v-4.675l-3.859 2.318a.75.75 0 0 1-.772-1.286l3.925-2.357-3.925-2.357a.75.75 0 0 1 .772-1.286l3.86 2.318V9.75a.75.75 0 0 1 .75-.75Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"ring-close\") {\n <svg\n id=\"ring-close\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path\n d=\"M20.255 20.932a.672.672 0 0 1-.47-.202l-8.533-8.534a.672.672 0 1 1 .95-.938l8.533 8.533a.672.672 0 0 1 0 .939.715.715 0 0 1-.48.202Z\"\n />\n <path\n d=\"M11.721 20.932a.714.714 0 0 0 .48-.202l8.533-8.534a.672.672 0 0 0-.949-.938l-8.533 8.533a.672.672 0 0 0 .469 1.141Z\"\n />\n </svg>\n }\n\n @if (icon === \"ring-close-fill\") {\n <svg\n id=\"ring-close-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path\n fill=\"#fff\"\n d=\"M29 16C29 23.1797 23.1797 29 16 29C8.8203 29 3 23.1797 3 16C3 8.8203 8.8203 3 16 3C23.1797 3 29 8.8203 29 16Z\"\n />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M16 28C22.6274 28 28 22.6274 28 16C28 9.37258 22.6274 4 16 4C9.37258 4 4 9.37258 4 16C4 22.6274 9.37258 28 16 28ZM16 29C23.1797 29 29 23.1797 29 16C29 8.8203 23.1797 3 16 3C8.8203 3 3 8.8203 3 16C3 23.1797 8.8203 29 16 29Z\"\n />\n <path\n fill=\"#1076DB\"\n d=\"M20.2545 20.9324C20.0775 20.9294 19.9088 20.8565 19.7852 20.7298L11.2518 12.1964C11.05 12.0347 10.9589 11.7714 11.0176 11.5195C11.0763 11.2676 11.2744 11.0717 11.5269 11.0159C11.7795 10.96 12.0417 11.0541 12.2012 11.2578L20.7345 19.7911C20.9892 20.0522 20.9892 20.4687 20.7345 20.7298C20.6057 20.8553 20.4343 20.9277 20.2545 20.9324Z\"\n />\n <path\n fill=\"#1076DB\"\n d=\"M11.721 20.9324C11.9008 20.9277 12.0723 20.8553 12.201 20.7298L20.7344 12.1964C20.9447 11.9278 20.9203 11.5442 20.6777 11.3044C20.4351 11.0645 20.0513 11.0445 19.785 11.2578L11.2517 19.7911C11.0643 19.9831 11.009 20.268 11.1111 20.5161C11.2131 20.7642 11.4528 20.9279 11.721 20.9324V20.9324Z\"\n />\n </svg>\n }\n\n @if (icon === \"ring-plus-fill\") {\n <svg\n id=\"ring-plus-fill\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n fill=\"none\"\n >\n <path fill=\"#fff\" d=\"M29 16c0 7.18-5.82 13-13 13S3 23.18 3 16 8.82 3 16 3s13 5.82 13 13Z\" />\n <path\n fill=\"#1076DB\"\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path fill=\"#1076DB\" d=\"M16 9h1v13h-1z\" />\n <path fill=\"#1076DB\" d=\"M10 15h13v1H10z\" />\n </svg>\n }\n\n @if (icon === \"ring-plus\") {\n <svg\n id=\"ring-plus\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16 28c6.627 0 12-5.373 12-12S22.627 4 16 4 4 9.373 4 16s5.373 12 12 12Zm0 1c7.18 0 13-5.82 13-13S23.18 3 16 3 3 8.82 3 16s5.82 13 13 13Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M16 9h1v13h-1z\" />\n <path d=\"M10 15h13v1H10z\" />\n </svg>\n }\n\n @if (icon === \"rosette\") {\n <svg\n id=\"rosette\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16.439 20.144a5.925 5.925 0 0 1-5.92-5.919 5.925 5.925 0 0 1 5.92-5.919 5.926 5.926 0 0 1 5.92 5.92 5.926 5.926 0 0 1-5.92 5.918Zm9.385-5.862a.754.754 0 0 0 .532-.305.655.655 0 0 0 .112-.53l-.61-2.758a.656.656 0 0 0-.327-.432.746.746 0 0 0-.61-.05l-2.295.786c-.158-.3-.339-.586-.537-.86l1.73-1.705a.755.755 0 0 0 .227-.572.655.655 0 0 0-.242-.483L21.61 5.596a.65.65 0 0 0-.524-.136.753.753 0 0 0-.51.34L19.27 7.845a6.993 6.993 0 0 0-1.017-.357l.273-2.41a.756.756 0 0 0-.182-.586.654.654 0 0 0-.493-.224h-2.824a.654.654 0 0 0-.492.224.752.752 0 0 0-.183.585l.273 2.411c-.349.094-.687.21-1.01.354l-1.304-2.04a.751.751 0 0 0-.512-.34.65.65 0 0 0-.523.135L9.082 7.373a.655.655 0 0 0-.242.483c-.011.211.071.42.226.572l1.727 1.7c-.199.274-.381.561-.541.863l-2.287-.785a.748.748 0 0 0-.611.051.657.657 0 0 0-.327.432l-.61 2.759a.65.65 0 0 0 .112.529c.123.17.318.281.533.305l2.407.255c.013.28.04.554.085.824l-2.304.748a.747.747 0 0 0-.457.41.65.65 0 0 0 0 .54l1.169 2.571c.077.17.227.299.41.356a.754.754 0 0 0 .606-.078l2.074-1.24c.234.283.493.54.767.784l-3.375 5.905 3.83-.958.996 2.987 3.047-5.338 3.046 5.338.998-2.987 3.83.958-3.293-5.76c.338-.28.65-.592.93-.931l2.078 1.241a.752.752 0 0 0 .607.08.666.666 0 0 0 .41-.357l1.17-2.572a.65.65 0 0 0 0-.538.75.75 0 0 0-.459-.411l-2.309-.75a7.17 7.17 0 0 0 .084-.821l2.415-.256Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"rule\") {\n <svg\n id=\"rule\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M15.467 8.533v7.574a2.666 2.666 0 0 0 0 5.226v2.134h1.067V21.28a2.666 2.666 0 0 0 0-5.227v-7.52h-1.067Zm-4.265 14.934v-6.454a2.666 2.666 0 0 0 0-5.226V9.6h-1.067v2.24a2.666 2.666 0 0 0 0 5.227v6.4h1.067ZM9.069 14.4a1.6 1.6 0 1 1 3.2 0 1.6 1.6 0 0 1-3.2 0Zm12.263-1.6a1.6 1.6 0 1 0 0 3.2 1.6 1.6 0 0 0 0-3.2Zm-.534 10.667h1.067v-6.454a2.666 2.666 0 0 0 0-5.226V9.6h-1.067v2.24a2.666 2.666 0 0 0 0 5.227v6.4ZM16 20.267a1.6 1.6 0 1 0 0-3.2 1.6 1.6 0 0 0 0 3.2Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"search\") {\n <svg\n id=\"search\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m19.114 18.485 3.083 3.2a.917.917 0 0 1 0 1.227.981.981 0 0 1-1.366 0l-3.2-3.2a5.472 5.472 0 0 1-2.634.683 5.397 5.397 0 1 1 5.397-5.333 5.227 5.227 0 0 1-1.28 3.423Zm-5.473-6.65a3.488 3.488 0 0 0-2.11 3.27 3.467 3.467 0 0 0 3.476 3.423 3.488 3.488 0 1 0-1.366-6.693Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"seats\") {\n <svg\n id=\"seats\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M9.602 8.001c-.882 0-1.6.717-1.6 1.599s.718 1.6 1.599 1.6c.882 0 1.601-.717 1.601-1.599s-.718-1.6-1.6-1.6m0 4.267A2.67 2.67 0 0 1 6.935 9.6a2.67 2.67 0 0 1 2.666-2.667 2.67 2.67 0 0 1 2.667 2.668A2.67 2.67 0 0 1 9.6 12.268M16.002 8.002c-.882 0-1.6.717-1.6 1.6a1.6 1.6 0 0 0 1.6 1.599c.427 0 .83-.166 1.132-.468a1.602 1.602 0 0 0-1.132-2.73m0 4.266a2.67 2.67 0 0 1-2.666-2.667 2.67 2.67 0 0 1 2.667-2.668 2.671 2.671 0 0 1 2.666 2.668 2.65 2.65 0 0 1-.78 1.886 2.65 2.65 0 0 1-1.886.78M9.601 14.401a1.601 1.601 0 0 0-.001 3.2c.882 0 1.601-.718 1.601-1.6 0-.882-.718-1.6-1.6-1.6m0 4.267A2.671 2.671 0 0 1 6.934 16 2.67 2.67 0 0 1 9.6 13.333a2.67 2.67 0 0 1 2.667 2.668A2.67 2.67 0 0 1 9.6 18.668M18.135 16.002A2.134 2.134 0 1 1 13.866 16a2.134 2.134 0 0 1 4.269.002\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M16.001 14.402a1.601 1.601 0 0 0 0 3.2c.881 0 1.6-.718 1.6-1.6 0-.882-.717-1.6-1.6-1.6m0 4.267A2.671 2.671 0 0 1 13.335 16a2.67 2.67 0 0 1 2.667-2.667 2.67 2.67 0 0 1 2.666 2.668 2.67 2.67 0 0 1-2.667 2.667\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22.402 8.003c-.882 0-1.6.717-1.6 1.6a1.6 1.6 0 0 0 1.599 1.599c.427 0 .83-.166 1.133-.468a1.602 1.602 0 0 0-1.132-2.73m0 4.266h-.001a2.67 2.67 0 0 1-2.666-2.667 2.67 2.67 0 0 1 2.667-2.668 2.671 2.671 0 0 1 2.667 2.668c0 .713-.278 1.383-.781 1.887a2.657 2.657 0 0 1-1.886.78\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M24.534 16.003a2.134 2.134 0 1 1-4.268-.002 2.134 2.134 0 0 1 4.268.002\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22.4 14.403a1.601 1.601 0 0 0 0 3.2c.882 0 1.6-.718 1.6-1.6 0-.882-.717-1.6-1.6-1.6m0 4.267a2.671 2.671 0 0 1-2.666-2.668 2.669 2.669 0 0 1 2.667-2.667 2.671 2.671 0 0 1 2.667 2.668A2.67 2.67 0 0 1 22.4 18.67M11.733 22.401a2.133 2.133 0 1 1-4.267 0 2.133 2.133 0 0 1 4.268 0\"\n clip-rule=\"evenodd\"\n />\n <mask\n id=\"b\"\n width=\"7\"\n height=\"7\"\n x=\"6\"\n y=\"19\"\n maskUnits=\"userSpaceOnUse\"\n style=\"mask-type: luminance\"\n >\n <path fill-rule=\"evenodd\" d=\"M6.934 19.734h5.333v5.334H6.934v-5.334Z\" clip-rule=\"evenodd\" />\n </mask>\n <g mask=\"url(#b)\">\n <path\n fill-rule=\"evenodd\"\n d=\"M9.599 20.8A1.595 1.595 0 0 0 8 22.4c0 .882.717 1.6 1.599 1.6s1.6-.717 1.601-1.6c0-.881-.718-1.6-1.6-1.6h-.001Zm0 4.268a2.668 2.668 0 0 1-1.885-4.554 2.65 2.65 0 0 1 1.884-.78H9.6a2.669 2.669 0 0 1 2.667 2.667 2.671 2.671 0 0 1-2.668 2.667Z\"\n clip-rule=\"evenodd\"\n />\n </g>\n <path\n fill-rule=\"evenodd\"\n d=\"M18.134 22.402a2.133 2.133 0 1 1-4.267 0 2.133 2.133 0 0 1 4.267 0\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M16 20.802a1.595 1.595 0 0 0-1.6 1.599c0 .882.717 1.6 1.6 1.601.881 0 1.6-.718 1.6-1.6 0-.882-.718-1.6-1.6-1.6Zm0 4.267a2.668 2.668 0 0 1-1.885-4.554 2.65 2.65 0 0 1 1.883-.78h.003a2.669 2.669 0 0 1 2.666 2.667A2.671 2.671 0 0 1 16 25.069ZM24.533 22.403a2.133 2.133 0 1 1-4.267 0 2.133 2.133 0 0 1 4.267 0\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22.399 20.803a1.595 1.595 0 0 0-1.6 1.599c0 .882.718 1.6 1.6 1.601.882 0 1.6-.718 1.6-1.6 0-.882-.717-1.6-1.6-1.6Zm0 4.267a2.668 2.668 0 0 1-1.885-4.554 2.65 2.65 0 0 1 1.884-.78h.002a2.669 2.669 0 0 1 2.667 2.667 2.671 2.671 0 0 1-2.668 2.667Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"settings\") {\n <svg\n id=\"settings\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M23.819 16.768v-1.344a1.067 1.067 0 0 0-.864-1.067l-1.259-.181a6.1 6.1 0 0 0-.352-.864l.758-1.013c.32-.425.28-1.021-.096-1.398l-.95-.949a1.067 1.067 0 0 0-1.397-.107l-1.067.768a5.509 5.509 0 0 0-.853-.352l-.181-1.258a1.067 1.067 0 0 0-1.067-.918h-1.344a1.067 1.067 0 0 0-1.067.918L13.9 10.26c-.294.094-.58.211-.853.352l-1.067-.768a1.067 1.067 0 0 0-1.397.107l-.95.95a1.067 1.067 0 0 0-.096 1.397l.758 1.013c-.14.279-.257.567-.352.864l-1.259.181c-.53.075-.923.532-.917 1.067v1.344c-.006.535.387.992.917 1.067l1.259.181c.095.293.213.578.352.853l-.758 1.067a1.067 1.067 0 0 0 .096 1.387l.95.96c.376.376.973.417 1.397.096l1.067-.768c.271.149.557.27.853.362l.181 1.259c.075.53.532.923 1.067.917h1.29c.536.006.992-.387 1.067-.917l.182-1.259c.296-.092.581-.213.853-.362l1.067.768c.424.32 1.02.28 1.397-.096l.95-.96c.37-.375.41-.965.095-1.387l-.757-1.067c.14-.275.257-.56.352-.853l1.259-.181c.53-.075.922-.532.917-1.067Zm-7.2 4.181-.267 1.867h-.981l-.267-1.867-.757-.245a5.093 5.093 0 0 1-.672-.288l-.715-.352-1.504 1.13-.693-.693 1.13-1.514-.362-.704a5.236 5.236 0 0 1-.277-.672l-.246-.758-1.866-.266v-.982l1.866-.266.246-.758c.074-.23.167-.456.277-.672l.363-.714-1.131-1.504.693-.694 1.504 1.131.715-.363c.215-.113.44-.206.672-.277l.757-.245.267-1.867h.981l.267 1.867.757.245c.233.07.458.162.672.277l.715.363 1.504-1.13.693.693-1.13 1.504.362.714c.11.216.204.441.278.672l.245.758 1.867.266v.982l-1.867.266-.245.758a5.25 5.25 0 0 1-.278.672l-.362.704 1.13 1.514-.693.694-1.504-1.131-.715.352c-.216.114-.44.21-.672.288l-.757.245Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M13.152 16.096a2.667 2.667 0 1 1 5.334 0 2.667 2.667 0 0 1-5.334 0Zm4 0a1.333 1.333 0 1 0-2.667 0 1.333 1.333 0 0 0 2.667 0Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"shopping-cart\") {\n <svg\n id=\"shopping-cart\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M22.524 9.674a1 1 0 0 1 .868 1.117L22.216 20.2H9.784l-1.027-8.194a.387.387 0 0 0-.384-.34h-3.04a1 1 0 1 1 0-2h3.04a2.387 2.387 0 0 1 2.368 2.093l.808 6.441h8.901l.957-7.657a1 1 0 0 1 1.117-.869ZM11.733 25.6a2.133 2.133 0 1 0 0-4.267 2.133 2.133 0 0 0 0 4.267ZM20.267 25.6a2.133 2.133 0 1 0 0-4.267 2.133 2.133 0 0 0 0 4.267Z\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M22.4 12.233H9.6v-1h12.8v1ZM21.334 15.967H10.667v-1h10.667v1Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M13.366 18.667v-6.934h1v6.934h-1ZM17.634 18.667v-6.934h1v6.934h-1Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"sorter\") {\n <svg\n id=\"sorter\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M16.146 10.646a.5.5 0 0 1 .708 0l4.5 4.5a.5.5 0 0 1-.708.708L17 12.207V20.5a.5.5 0 0 1-1 0v-8.293l-3.646 3.647a.5.5 0 0 1-.708-.708l4.5-4.5Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"star\") {\n <svg\n id=\"star\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"m16.811 8.83 2.121 4.298 4.745.69a.533.533 0 0 1 .294.91l-3.432 3.345.81 4.724a.533.533 0 0 1-.773.562l-4.242-2.23-4.244 2.23a.532.532 0 0 1-.772-.562l.809-4.724-3.43-3.346a.533.533 0 0 1 .294-.91l4.743-.689 2.122-4.297a.532.532 0 0 1 .955 0\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"switch\") {\n <svg\n id=\"switch\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n d=\"M17.597 14.933a.528.528 0 0 0-.533.534v2.666h-6.71l2.294-2.293a.528.528 0 0 0 0-.757.528.528 0 0 0-.758 0l-3.2 3.2a.481.481 0 0 0-.117.181.533.533 0 0 0 0 .405.38.38 0 0 0 .118.171l3.2 3.2a.5.5 0 0 0 .373.16.5.5 0 0 0 .373-.16.528.528 0 0 0 0-.757L10.354 19.2h7.243a.528.528 0 0 0 .534-.533v-3.2a.528.528 0 0 0-.534-.534Z\"\n />\n <path\n d=\"M24.488 12.47a.534.534 0 0 0 0-.406.488.488 0 0 0-.117-.17l-3.2-3.2a.528.528 0 0 0-.758 0 .528.528 0 0 0 0 .757l2.294 2.282h-7.243a.528.528 0 0 0-.533.534v3.2c0 .298.234.533.533.533a.528.528 0 0 0 .533-.533V12.8h6.71l-2.294 2.293a.528.528 0 0 0 0 .758c.107.096.246.149.384.149a.5.5 0 0 0 .374-.16l3.2-3.2a.38.38 0 0 0 .117-.17Z\"\n />\n </svg>\n }\n\n @if (icon === \"three-dots\") {\n <svg\n id=\"three-dots\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M8.96 16a3.2 3.2 0 1 1-6.4 0 3.2 3.2 0 0 1 6.4 0ZM16 12.8a3.2 3.2 0 1 0 0 6.4 3.2 3.2 0 0 0 0-6.4Zm7.04 3.2a3.2 3.2 0 1 1 6.4 0 3.2 3.2 0 0 1-6.4 0Z\"\n clip-rule=\"evenodd\"\n />\n </svg>\n }\n\n @if (icon === \"three-dots-small\") {\n <svg\n id=\"three-dots-small\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n >\n <path\n d=\"M5 8.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM13.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3ZM8.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z\"\n />\n </svg>\n }\n\n @if (icon === \"waiting-approval\") {\n <svg\n id=\"waiting-approval\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g fill-rule=\"evenodd\" clip-path=\"url(#a)\" clip-rule=\"evenodd\">\n <path\n d=\"m10.896 16 3.904-2.848V9.6h1.067V8.533H4.134V9.6H5.2v3.552L9.094 16 5.2 18.848V22.4H4.134v1.067h11.733V22.4H14.8v-3.552L10.896 16Zm-4.628-3.392L10 15.338l3.733-2.73V9.6H6.268v3.008Zm7.466 9.792v-3.008l-3.733-2.73-3.733 2.73V22.4h7.466Zm-6.4-10.336v-.33h5.333v.33l-2.666 1.952-2.667-1.952Zm5.333 8.203H7.334v1.066h5.333v-1.066ZM26.573 11.18a1 1 0 0 1 .247 1.393l-5.713 8.181a1 1 0 0 1-1.564.095l-3.287-3.665a1 1 0 0 1 1.488-1.335l2.445 2.726 4.991-7.147a1 1 0 0 1 1.393-.248Z\"\n />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"waiting-decline\") {\n <svg\n id=\"waiting-decline\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g fill-rule=\"evenodd\" clip-path=\"url(#a)\" clip-rule=\"evenodd\">\n <path\n d=\"m10.896 16 3.904-2.848V9.6h1.067V8.533H4.134V9.6H5.2v3.552L9.094 16 5.2 18.848V22.4H4.134v1.067h11.733V22.4H14.8v-3.552L10.896 16Zm-4.628-3.392L10 15.338l3.733-2.73V9.6H6.268v3.008Zm7.466 9.792v-3.008l-3.733-2.73-3.733 2.73V22.4h7.466Zm-6.4-10.336v-.33h5.333v.33l-2.666 1.952-2.667-1.952Zm5.333 8.203H7.334v1.066h5.333v-1.066ZM17.293 11.293a1 1 0 0 1 1.414 0l3.793 3.793 3.793-3.793a1 1 0 0 1 1.414 1.414L23.914 16.5l3.793 3.793a1 1 0 0 1-1.414 1.414L22.5 17.914l-3.793 3.793a1 1 0 0 1-1.414-1.414l3.793-3.793-3.793-3.793a1 1 0 0 1 0-1.414Z\"\n />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"zoom-in\") {\n <svg\n id=\"zoom-in\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g clip-path=\"url(#a)\">\n <path\n fill-rule=\"evenodd\"\n d=\"M15.5 23a8.5 8.5 0 1 0 0-17 8.5 8.5 0 0 0 0 17Zm0 2C21.299 25 26 20.299 26 14.5S21.299 4 15.5 4 5 8.701 5 14.5 9.701 25 15.5 25Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M21.293 21.293a1 1 0 0 1 1.414 0l5 5a1 1 0 0 1-1.414 1.414l-5-5a1 1 0 0 1 0-1.414Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M15 8h1v13h-1z\" />\n <path d=\"M9 14h13v1H9z\" />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n\n @if (icon === \"zoom-out\") {\n <svg\n id=\"zoom-out\"\n [class]=\"_classList | async\"\n aria-hidden=\"true\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n height=\"32\"\n >\n <g clip-path=\"url(#a)\">\n <path\n fill-rule=\"evenodd\"\n d=\"M15.5 23a8.5 8.5 0 1 0 0-17 8.5 8.5 0 0 0 0 17Zm0 2C21.299 25 26 20.299 26 14.5S21.299 4 15.5 4 5 8.701 5 14.5 9.701 25 15.5 25Z\"\n clip-rule=\"evenodd\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M21.293 21.293a1 1 0 0 1 1.414 0l5 5a1 1 0 0 1-1.414 1.414l-5-5a1 1 0 0 1 0-1.414Z\"\n clip-rule=\"evenodd\"\n />\n <path d=\"M9 14h12v1H9z\" />\n </g>\n <defs>\n <clipPath id=\"a\"><path d=\"M0 0h32v32H0z\" /></clipPath>\n </defs>\n </svg>\n }\n</ng-container>\n", styles: [".fudis-icon{box-sizing:border-box;display:inline-flex;align-items:center;transition:all .6s ease}.fudis-icon__rotate__flip-180{transform:rotate(180deg)}.fudis-icon__rotate__cw-90{transform:rotate(90deg)}.fudis-icon__rotate__ccw-90{transform:rotate(-90deg)}.fudis-icon__lg{width:calc(2rem / var(--fudis-rem-multiplier));height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-icon__sm{width:calc(1rem / var(--fudis-rem-multiplier));height:calc(1rem / var(--fudis-rem-multiplier))}.fudis-icon__color__yellow{fill:#f1c40f}.fudis-icon__color__red{fill:#b83c2e}.fudis-icon__color__gray-dark{fill:#484848}.fudis-icon__color__gray-light{fill:#d4d4d4}.fudis-icon__color__primary{fill:var(--fudis-color-primary)}.fudis-icon__color__primary-dark{fill:var(--fudis-color-primary-dark)}.fudis-icon__color__green{fill:#1e874b}.fudis-icon__color__white{fill:#fff}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { _classes: [{
type: HostBinding,
args: ['class']
}], icon: [{
type: Input,
args: [{ required: true }]
}], color: [{
type: Input
}], rotate: [{
type: Input
}] } });
class FudisIdService {
constructor() {
this._idData = {
components: {
alert: [],
autocomplete: [],
'body-text': [],
checkbox: [],
button: [],
'autocomplete-multi-select': [],
datepicker: [],
dialog: [],
dropdown: [],
'error-message': [],
expandable: [],
fieldset: [],
form: [],
guidance: [],
heading: [],
'localized-text-group': [],
link: [],
pagination: [],
popover: [],
section: [],
'text-area': [],
'text-input': [],
'validator-error-message': [],
},
parents: {
breadcrumbs: {},
'checkbox-group': {},
'language-badge-group': {},
'radio-button-group': {},
},
grandParents: {
'description-list': {},
'dropdown-menu': {},
select: {},
multiselect: {},
},
};
}
/**
* Get collected ID data
*/
getIdData() {
return this._idData;
}
/* ------------------
* Basic Id for single leveled components. E. g. Button
* ------------------
* /
/**
* Get an id and add it to collection for a single component. E.g. 'fudis-button-1'
*/
getNewId(componentType) {
const orderNumber = this._idData.components[componentType].length + 1;
const newId = `fudis-${componentType}-${orderNumber}`;
this.addNewId(componentType, newId);
return newId;
}
/**
* Add an id to collection of single components
*/
addNewId(componentType, newId) {
this._idData.components[componentType].push(newId);
}
/* ------------------
* Ids' for two leveled components. E. g. Breadcrumbs and Breadcrumbs Item
* ------------------
*/
/**
* Get an id and add it to collection for a parent component. E.g. 'fudis-breadcrumbs-1'
*/
getNewParentId(componentType) {
const orderNumber = Object.keys(this._idData.parents[componentType]).length + 1;
const newId = `fudis-${componentType}-${orderNumber}`;
this.addNewParentId(componentType, newId);
return newId;
}
/**
* Add id to collection of parents
*/
addNewParentId(componentType, newId) {
this._idData.parents[componentType][newId] = [];
}
/**
* Get an id and add it to collection for a child component. E.g. 'fudis-breadcrumbs-1-item-1'
*/
getNewChildId(parentType, parentId) {
let newId = '';
const orderNumber = Object.keys(this._idData.parents[parentType]?.[parentId]).length + 1;
if (orderNumber) {
newId = `${parentId}-item-${orderNumber}`;
this._idData.parents[parentType][parentId].push(newId);
}
return newId;
}
getAllChildrenIds(parentType, parentId) {
return this._idData.parents[parentType]?.[parentId];
}
/**
* Add id to collection of parents children
*/
addNewChildId(parentType, parentId, customId) {
this._idData.parents[parentType]?.[parentId].push(customId);
}
/* ------------------
* Ids' for three leveled components. E. g. Select, SelectGroup and Select Option and DL, DL Item, DL Term & Details
* ------------------
*/
/**
* Get an id and add it to collection for a grand parent type components
*/
getNewGrandParentId(componentType) {
let newId = '';
const orderNumber = Object.keys(this._idData.grandParents[componentType]).length + 1;
newId = `fudis-${componentType}-${orderNumber}`;
this.addNewGrandParentId(componentType, newId);
return newId;
}
/**
* Add grand parent id to data collection
*/
addNewGrandParentId(componentType, newId) {
if (componentType === 'description-list') {
const newGrandParent = {
id: newId,
items: {},
};
this._idData.grandParents[componentType][newId] = newGrandParent;
}
else if (componentType === 'dropdown-menu') {
const newGrandParent = {
id: newId,
groups: {},
nonGroupedOptions: [],
};
this._idData.grandParents[componentType][newId] = newGrandParent;
}
else {
const newGrandParent = {
id: newId,
groups: {},
nonGroupedOptions: [],
};
this._idData.grandParents[componentType][newId] = newGrandParent;
}
}
/**
* Get an id and add it to collection for first child of grandparent. E.g. Select Group or
* Description List Item
*/
getNewGroupId(componentType, parentId) {
let newId = '';
if (this._idData.grandParents[componentType]?.[parentId]) {
if (componentType === 'description-list') {
const orderNumber = Object.keys(this._idData.grandParents[componentType][parentId].items).length + 1;
newId = `${parentId}-item-${orderNumber}`;
this._idData.grandParents[componentType][parentId].items[newId] = {
term: [],
details: [],
};
}
else {
const orderNumber = Object.keys(this._idData.grandParents[componentType][parentId].groups).length + 1;
newId = `${parentId}-group-${orderNumber}`;
this._idData.grandParents[componentType][parentId].groups[newId] = [];
}
}
return newId;
}
/**
* Add group id of grand parent to data collection
*/
addNewGroupId(componentType, parentId, newId) {
if (this._idData.grandParents[componentType]?.[parentId]) {
if (componentType === 'description-list') {
this._idData.grandParents[componentType][parentId].items[newId] = {
term: [],
details: [],
};
}
else {
this._idData.grandParents[componentType][parentId].groups[newId] = [];
}
}
}
getNewDropdownMenuId(parentId, groupParentId) {
let newId = '';
if (groupParentId) {
const orderNumber = this._idData.grandParents['dropdown-menu'][parentId].groups[groupParentId].length + 1;
newId = `${groupParentId}-option-${orderNumber}`;
this._idData.grandParents['dropdown-menu'][parentId].groups[groupParentId].push(newId);
}
else {
const orderNumber = this._idData.grandParents['dropdown-menu'][parentId].nonGroupedOptions.length + 1;
newId = `${parentId}-option-${orderNumber}`;
this._idData.grandParents['dropdown-menu'][parentId].nonGroupedOptions.push(newId);
}
return newId;
}
/**
* Get an id and add it to collection for Select Options -->
* fudis-select-4-group-2-option-g3n3ratedh4sh
*/
getNewSelectOptionId(label, selectType, selectParentId, groupParentId) {
const newId = FudisIdService.createSelectOptionId(selectParentId, label);
if (groupParentId) {
this._idData.grandParents[selectType][selectParentId].groups[groupParentId].push(newId);
}
else {
this._idData.grandParents[selectType][selectParentId].nonGroupedOptions.push(newId);
}
return newId;
}
static createSelectOptionId(id, label) {
return `${id}-option-${FudisIdService.hashLabel(label)}`;
}
/**
* Djb2 hash for hashing the label for the option id. It doesn't have to be this, it can be
* anything as long as it's fast and short
*/
static hashLabel(label) {
let hash = 0;
for (let i = 0; i < label.length; i++) {
hash = (hash * 31 + label.charCodeAt(i)) >>> 0;
}
return hash.toString(36);
}
/**
* Get an id and add it to collection for Descrition List Term & Details -->
* fudis-description-list-1-item-1-term & fudis-description-list-1-item-1-details-1
*/
getNewDlGrandChilId(childType, parentDlId, parentItemId) {
let newId = '';
if (this._idData.grandParents['description-list']?.[parentDlId]?.items[parentItemId]) {
const orderNumber = this._idData.grandParents['description-list'][parentDlId].items[parentItemId][childType]
.length + 1;
newId = `${parentItemId}-${childType}-${orderNumber}`;
this._idData.grandParents['description-list'][parentDlId].items[parentItemId][childType].push(newId);
}
return newId;
}
getDlGrandChildrensIds(childType, parentDlId, parentItemId) {
const allGrandChildrensIds = this._idData.grandParents['description-list']?.[parentDlId]?.items[parentItemId][childType];
return allGrandChildrensIds;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisIdService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisIdService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisIdService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* This file contains translations for static translation keys used by Fudis components. These are
* not supposed to be modified by the using application.
*/
/**
* English translations
*/
const en = {
BREADCRUMBS: { PREFIX: 'Breadcrumbs' },
REQUIRED: 'Required',
DATEPICKER: {
CALENDAR: 'Calendar',
CLOSE: 'Close calendar',
OPEN: 'Open calendar',
PLACEHOLDER: 'dd.mm.yyyy',
PREV_MONTH: 'Previous month',
NEXT_MONTH: 'Next month',
PREV_YEAR: 'Previous year',
NEXT_YEAR: 'Next year',
PREV_MULTIYEAR: 'Previous 24 years',
NEXT_MULTIYEAR: 'Next 24 years',
SWITCH_MONTH_VIEW: 'Switch to month view',
SWITCH_MULTIYEAR_VIEW: 'Switch to year view',
VALIDATION: {
START_DATE_INVALID: 'Start date cannot be after end date',
END_DATE_INVALID: 'End date cannot be before start date',
DATE_PARSE: 'Invalid date format',
},
},
DIALOG: { CLOSE: 'Close' },
DESCRIPTION_LIST_ITEM_DETAILS: {
EMPTY_STATE: 'Information is not available',
},
DROPDOWNMENU: {
ITEM: {
DISABLED: 'Disabled',
},
},
ERROR_SUMMARY: {
TITLE: 'The information is incomplete or incorrect. Please correct the following items:',
},
INPUT_WITH_LANGUAGE_OPTIONS: {
LANGUAGE: 'Language',
MISSING: 'Missing',
MAX_LENGTH: 'characters used',
},
LINK: {
EXTERNAL_LINK: '(opens in a new tab)',
},
PAGINATION: {
BUTTON: {
NEXT: {
LABEL: 'Next',
ARIA_LABEL: 'Next, page',
},
PREVIOUS: {
LABEL: 'Previous',
ARIA_LABEL: 'Previous, page',
},
},
PREFIX: 'Pagination',
OPENED_PAGE: 'Opened page',
CURRENT_PAGE: 'current page',
LAST_PAGE: 'last page',
},
SELECT: {
DISABLED: 'Disabled',
MULTISELECT: {
REMOVE_ITEM: 'Remove selection',
REMOVE_ITEMS: 'Remove selections',
},
AUTOCOMPLETE: {
CLEAR: 'Clear selection',
NO_RESULTS: 'No results found',
SHOWING: 'Showing',
RESULTS: 'results',
},
},
IMAGE: {
FUNIDATA_LOGO: 'Funidata homepage',
},
ATTENTION: 'Attention',
LOADING_SPINNER: {
VISIBLE_LABEL: 'Loading',
PAGE_LOADING: 'Page is loading',
PAGE_LOAD_FINISHED: 'Page load finished',
},
LANGUAGE_BADGE: {
ARIA_LABEL: {
FI: 'Change translation to Finnish',
SV: 'Change translation to Swedish',
EN: 'Change translation to English',
TRANSLATIONS: 'Translations',
SELECTED: '(Selected)',
MISSING_TRANSLATION: '(Missing translation)',
},
},
TEXTAREA: {
MAX_LENGTH: 'characters used',
},
TEXTINPUT: {
MAX_LENGTH: 'characters used',
},
ALERT: {
HEADING_LABEL: 'Notifications - Number of notifications',
},
};
/**
* Finnish translations
*/
const fi = {
BREADCRUMBS: { PREFIX: 'Murupolku' },
REQUIRED: 'Pakollinen',
DATEPICKER: {
CALENDAR: 'Kalenteri',
CLOSE: 'Sulje kalenteri',
OPEN: 'Avaa kalenteri',
PLACEHOLDER: 'pp.kk.vvvv',
PREV_MONTH: 'Edellinen kuukausi',
NEXT_MONTH: 'Seuraava kuukausi',
PREV_YEAR: 'Edellinen vuosi',
NEXT_YEAR: 'Seuraava vuosi',
PREV_MULTIYEAR: 'Edelliset 24 vuotta',
NEXT_MULTIYEAR: 'Seuraavat 24 vuotta',
SWITCH_MONTH_VIEW: 'Vaihda kuukausinäkymään',
SWITCH_MULTIYEAR_VIEW: 'Vaihda vuosinäkymään',
VALIDATION: {
START_DATE_INVALID: 'Alkamispäivän on oltava ennen päättymispäivää',
END_DATE_INVALID: 'Päättymispäivän on oltava alkamispäivän jälkeen',
DATE_PARSE: 'Virheellinen päivämääräformaatti.',
},
},
DIALOG: { CLOSE: 'Sulje' },
DESCRIPTION_LIST_ITEM_DETAILS: {
EMPTY_STATE: 'Tietoa ei ole saatavilla',
},
DROPDOWNMENU: {
ITEM: {
DISABLED: 'Ei valittavissa',
},
},
ERROR_SUMMARY: {
TITLE: 'Tiedoissa on puutteita tai virheitä. Korjaa seuraavat kohdat:',
},
INPUT_WITH_LANGUAGE_OPTIONS: {
LANGUAGE: 'Kieli',
MISSING: 'Puuttuu',
MAX_LENGTH: 'merkkiä käytetty',
},
LINK: {
EXTERNAL_LINK: '(aukeaa uuteen välilehteen)',
},
PAGINATION: {
BUTTON: {
NEXT: {
LABEL: 'Seuraava',
ARIA_LABEL: 'Seuraava, sivu',
},
PREVIOUS: {
LABEL: 'Edellinen',
ARIA_LABEL: 'Edellinen, sivu',
},
},
PREFIX: 'Sivutus',
OPENED_PAGE: 'Avattu sivu',
CURRENT_PAGE: 'nykyinen sivu',
LAST_PAGE: 'viimeinen sivu',
},
SELECT: {
DISABLED: 'Ei valittavissa',
MULTISELECT: {
REMOVE_ITEM: 'Poista valinta',
REMOVE_ITEMS: 'Poista valintoja',
},
AUTOCOMPLETE: {
CLEAR: 'Tyhjennä valinta',
NO_RESULTS: 'Haku ei palauttanut yhtään tulosta',
SHOWING: 'Näytetään',
RESULTS: 'tulosta',
},
},
IMAGE: {
FUNIDATA_LOGO: 'Funidatan kotisivut',
},
ATTENTION: 'Huomio',
LOADING_SPINNER: {
VISIBLE_LABEL: 'Ladataan',
PAGE_LOADING: 'Sivua ladataan',
PAGE_LOAD_FINISHED: 'Sivu on ladattu',
},
LANGUAGE_BADGE: {
ARIA_LABEL: {
FI: 'Vaihda käännöskieleksi Suomi',
SV: 'Vaihda käännöskieleksi Ruotsi',
EN: 'Vaihda käännöskieleksi Englanti',
TRANSLATIONS: 'Käännöskielet',
SELECTED: '(Valittuna)',
MISSING_TRANSLATION: '(Käännös puuttuu)',
},
},
TEXTAREA: {
MAX_LENGTH: 'merkkiä käytetty',
},
TEXTINPUT: {
MAX_LENGTH: 'merkkiä käytetty',
},
ALERT: {
HEADING_LABEL: 'Ilmoitukset - Ilmoituksia yhteensä',
},
};
/**
* Swedish translations
*/
const sv = {
BREADCRUMBS: { PREFIX: 'Länkstig' },
REQUIRED: 'Obligatorisk',
DATEPICKER: {
CALENDAR: 'Kalender',
CLOSE: 'Stäng kalender',
OPEN: 'Öppna kalender',
PLACEHOLDER: 'dd.mm.åååå',
PREV_MONTH: 'Förra månaden',
NEXT_MONTH: 'Nästa månad',
PREV_YEAR: 'Förra året',
NEXT_YEAR: 'Nästa år',
PREV_MULTIYEAR: 'Tidigare 24 år',
NEXT_MULTIYEAR: 'Nästa 24 år',
SWITCH_MONTH_VIEW: 'Byta till månadsvy',
SWITCH_MULTIYEAR_VIEW: 'Byta till årsvy',
VALIDATION: {
START_DATE_INVALID: 'Startdatumet måste vara före slutdatumet',
END_DATE_INVALID: 'Slutdatumet måste vara efter startdatumet',
DATE_PARSE: 'Datumet format är felaktigt',
},
},
DIALOG: { CLOSE: 'Stäng' },
DESCRIPTION_LIST_ITEM_DETAILS: {
EMPTY_STATE: 'Ingen information tillgänglig',
},
DROPDOWNMENU: {
ITEM: {
DISABLED: 'Inaktiverad',
},
},
ERROR_SUMMARY: {
TITLE: 'Det finns brister eller fel i uppgifterna. Korrigera följande punkter:',
},
INPUT_WITH_LANGUAGE_OPTIONS: {
LANGUAGE: 'Språk',
MISSING: 'Saknas',
MAX_LENGTH: 'tecken använt',
},
PAGINATION: {
BUTTON: {
NEXT: {
LABEL: 'Nästa',
ARIA_LABEL: 'Nästa, sida',
},
PREVIOUS: {
LABEL: 'Föregående',
ARIA_LABEL: 'Föregående, sida',
},
},
PREFIX: 'Paginering',
OPENED_PAGE: 'Öppnad sida',
CURRENT_PAGE: 'nuvarande sida',
LAST_PAGE: 'sista sida',
},
SELECT: {
DISABLED: 'Inaktiverad',
MULTISELECT: {
REMOVE_ITEM: 'Ta bort val',
REMOVE_ITEMS: 'Ta bort valen',
},
AUTOCOMPLETE: {
CLEAR: 'Radera val',
NO_RESULTS: 'Inga sökresultat',
SHOWING: 'Visas',
RESULTS: 'resultat',
},
},
IMAGE: {
FUNIDATA_LOGO: 'Funidatas hemsida',
},
LINK: {
EXTERNAL_LINK: '(öppnas i en ny flik)',
},
ATTENTION: 'Observera',
LOADING_SPINNER: {
VISIBLE_LABEL: 'Laddar',
PAGE_LOADING: 'Sidan laddas',
PAGE_LOAD_FINISHED: 'Sidan har laddats klart',
},
LANGUAGE_BADGE: {
ARIA_LABEL: {
FI: 'Ändra översättning till Finska',
SV: 'Ändra översättning till Svenska',
EN: 'Ändra översättning till Engelska',
TRANSLATIONS: 'Översättningar',
SELECTED: 'Vald',
MISSING_TRANSLATION: '(Saknad översättning)',
},
},
TEXTAREA: {
MAX_LENGTH: 'tecken använt',
},
TEXTINPUT: {
MAX_LENGTH: 'tecken använt',
},
ALERT: {
HEADING_LABEL: 'Meddelanden - Totalt meddelanden',
},
};
class FudisTranslationService {
constructor() {
/**
* Signal for Application's translation texts, default language for translations is English
*/
this._appTranslationsSignal = signal(en, ...(ngDevMode ? [{ debugName: "_appTranslationsSignal" }] : /* istanbul ignore next */ []));
/**
* Application language, default language is English
*/
this._appLanguage = signal('en', ...(ngDevMode ? [{ debugName: "_appLanguage" }] : /* istanbul ignore next */ []));
/**
* Currently available languages in Fudis are Finnish, Swedish and English
*/
this._selectableLanguages = signal(['fi', 'sv', 'en'], ...(ngDevMode ? [{ debugName: "_selectableLanguages" }] : /* istanbul ignore next */ []));
}
/**
* Set language of repeated texts Fudis uses for its components. E. g. 'required' text of form
* components or help texts for screen readers for various buttons. Also as Error Summary
* Component reloads its errors if error's language has changed, here is defined component to NOT
* focus to the updated list on only language change.
*/
setLanguage(language) {
this._appLanguage.set(language);
if (language === 'en') {
this._appTranslationsSignal.set({ ...en });
}
else if (language === 'fi') {
this._appTranslationsSignal.set({ ...fi });
}
else {
this._appTranslationsSignal.set({ ...sv });
}
}
/**
* Get current language of Fudis configs
*/
getLanguage() {
return this._appLanguage();
}
/**
* Get current language as Signal
*/
getLanguageSignal() {
return this._appLanguage.asReadonly();
}
/**
* Get application's translation config values Signal
*/
getTranslations() {
return this._appTranslationsSignal.asReadonly();
}
/**
* Set which languages are visible in Language Badges
*/
setSelectableLanguages(languages) {
this._selectableLanguages.set([...languages]);
}
/**
* Get visible languages of Language Badges
*/
getSelectableLanguages() {
return this._selectableLanguages.asReadonly();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisTranslationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisTranslationService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
/**
* Displays the hierarchial navigation path to the current page.
*
* Use this component to help users understand their location and navigate back through parent
* levels.
*/
class BreadcrumbsComponent {
constructor(_translationService, _idService) {
this._translationService = _translationService;
this._idService = _idService;
/**
* Prefix for aria-label from Fudis translation keys
*/
this._breadcrumbsPrefix = new BehaviorSubject(this._translationService.getTranslations()().BREADCRUMBS.PREFIX);
this._id = this._idService.getNewParentId('breadcrumbs');
this._breadcrumbsPrefix.next(this._translationService.getTranslations()().BREADCRUMBS.PREFIX);
}
/**
* Getter for id
*/
get id() {
return this._id;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BreadcrumbsComponent, deps: [{ token: FudisTranslationService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: BreadcrumbsComponent, isStandalone: true, selector: "fudis-breadcrumbs", inputs: { label: "label" }, ngImport: i0, template: "<nav [attr.aria-label]=\"(_breadcrumbsPrefix | async) + ': ' + label\" [attr.id]=\"_id\">\n <ul class=\"fudis-breadcrumbs\">\n <ng-content select=\"fudis-breadcrumbs-item\" />\n </ul>\n</nav>\n", styles: [".fudis-breadcrumbs-item-host:last-of-type .fudis-breadcrumbs-item__icon{display:none}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BreadcrumbsComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-breadcrumbs', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AsyncPipe], template: "<nav [attr.aria-label]=\"(_breadcrumbsPrefix | async) + ': ' + label\" [attr.id]=\"_id\">\n <ul class=\"fudis-breadcrumbs\">\n <ng-content select=\"fudis-breadcrumbs-item\" />\n </ul>\n</nav>\n", styles: [".fudis-breadcrumbs-item-host:last-of-type .fudis-breadcrumbs-item__icon{display:none}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: FudisIdService }], propDecorators: { label: [{
type: Input,
args: [{ required: true }]
}] } });
/**
* Represents a single link within BreadcrumbsComponent. Must be a direct child of
* `fudis-breadcrumbs`.
*
* Use this component with descriptive link text to ensure understandability.
*/
class BreadcrumbsItemComponent {
constructor(_idService, _breadCrumbs) {
this._idService = _idService;
this._breadCrumbs = _breadCrumbs;
/**
* Binding host CSS class to component wrapper
*/
this._classes = 'fudis-breadcrumbs-item-host';
this._id = this._idService.getNewChildId('breadcrumbs', this._breadCrumbs.id);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BreadcrumbsItemComponent, deps: [{ token: FudisIdService }, { token: BreadcrumbsComponent, host: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: BreadcrumbsItemComponent, isStandalone: true, selector: "fudis-breadcrumbs-item", host: { properties: { "class": "this._classes" } }, ngImport: i0, template: "<li class=\"fudis-breadcrumbs-item\" [attr.id]=\"_id\">\n <ng-content select=\"a\" />\n <span class=\"fudis-breadcrumbs-item__icon\">\n <fudis-icon [icon]=\"'chevron'\" />\n </span>\n <ng-content select=\"fudis-body-text\" />\n</li>\n", dependencies: [{ kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BreadcrumbsItemComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-breadcrumbs-item', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [IconComponent], template: "<li class=\"fudis-breadcrumbs-item\" [attr.id]=\"_id\">\n <ng-content select=\"a\" />\n <span class=\"fudis-breadcrumbs-item__icon\">\n <fudis-icon [icon]=\"'chevron'\" />\n </span>\n <ng-content select=\"fudis-body-text\" />\n</li>\n" }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: BreadcrumbsComponent, decorators: [{
type: Host
}] }], propDecorators: { _classes: [{
type: HostBinding,
args: ['class']
}] } });
/**
* Displays standard body text content.
*
* Use this component to render paragraphs or informational text with consistent typography and
* spacing. Use clear, simple, and inclusive language.
*/
class BodyTextComponent {
constructor(_breadcrumbsItem, _idService) {
this._breadcrumbsItem = _breadcrumbsItem;
this._idService = _idService;
/**
* Class for the parent wrapper element
*/
this._classes = 'fudis-body-text-host';
/**
* Variant for the paragraph: 'lg-regular' | 'md-regular' | 'sm-regular' | 'lg-light' | 'md-light'
*/
this.variant = 'md-regular';
/**
* Text alignment: 'center' | 'left' | 'right'
*/
this.align = 'left';
/**
* To add default CSS class if app hasn't provided any variant
*/
this._defaultClass = new BehaviorSubject(true);
this._id = _idService.getNewId('body-text');
}
ngOnChanges(changes) {
if (changes.variant?.currentValue !== changes.variant?.previousValue) {
if (!changes.variant?.currentValue) {
this._defaultClass.next(true);
}
else {
this._defaultClass.next(false);
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BodyTextComponent, deps: [{ token: BreadcrumbsItemComponent, host: true, optional: true }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: BodyTextComponent, isStandalone: true, selector: "fudis-body-text", inputs: { variant: "variant", align: "align", lang: "lang" }, host: { properties: { "class": "this._classes" } }, usesOnChanges: true, ngImport: i0, template: "<p\n [id]=\"_id\"\n [class.fudis-body-text__default]=\"_defaultClass | async\"\n [attr.aria-current]=\"_breadcrumbsItem ? 'page' : null\"\n [attr.lang]=\"lang\"\n class=\"fudis-body-text fudis-body-text__{{ variant }} fudis-body-text__{{ align }}\"\n ><ng-content\n/></p>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BodyTextComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-body-text', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p\n [id]=\"_id\"\n [class.fudis-body-text__default]=\"_defaultClass | async\"\n [attr.aria-current]=\"_breadcrumbsItem ? 'page' : null\"\n [attr.lang]=\"lang\"\n class=\"fudis-body-text fudis-body-text__{{ variant }} fudis-body-text__{{ align }}\"\n ><ng-content\n/></p>\n" }]
}], ctorParameters: () => [{ type: BreadcrumbsItemComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: FudisIdService }], propDecorators: { _classes: [{
type: HostBinding,
args: ['class']
}], variant: [{
type: Input
}], align: [{
type: Input
}], lang: [{
type: Input
}] } });
class FudisAlertService {
constructor(_idService) {
this._idService = _idService;
/**
* Current alerts
*/
this._alerts = new BehaviorSubject([]);
}
/**
* To add new alert, which will be rendered by Alert Group component. Note that 'id' doesn't
* necessarily need to be unique. All errors with the same 'id' can be dismissed with
* dismissAlert() using that particular 'id'.
*/
addAlert(newAlert) {
const htmlId = this._idService.getNewId('alert');
const currentAlerts = this._alerts.getValue();
this._alerts.next([
...currentAlerts,
{ ...newAlert, htmlId, buttonId: `${htmlId}-button`, initialFocus: true },
]);
}
/**
* To dismiss alert by 'id' provided in 'addAlert()'. It will dismiss all alerts matching the
* 'id'.
*/
dismissAlert(id) {
const filteredAlerts = this._alerts.getValue().filter((alert) => alert.id !== id);
this._alerts.next(filteredAlerts);
}
/**
* Dismisses only one alert from alert's close button click in the UI
*/
dismissAlertFromButton(id) {
const filteredAlerts = this._alerts.getValue().filter((alert) => alert.buttonId !== id);
this._alerts.next(filteredAlerts);
}
/**
* Dismiss all alerts
*/
dismissAll() {
this._alerts.next([]);
}
/**
* Get alerts as an Observable
*/
get alerts() {
return this._alerts;
}
/**
* Set 'initialFocus' attribute of alert to false, so that if same alert with link is rendered
* again, the initial focus will not jump there anymore.
*/
updateAlertLinkFocusState(htmlId) {
let alertIndex;
const tempAlerts = this._alerts.getValue();
tempAlerts.forEach((value, index) => {
if (value.htmlId === htmlId) {
alertIndex = index;
}
if (alertIndex !== -1) {
tempAlerts[index].initialFocus = false;
this._alerts.next(tempAlerts);
}
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisAlertService, deps: [{ token: FudisIdService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisAlertService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisAlertService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: FudisIdService }] });
class FudisFocusService {
constructor(_document) {
this._document = _document;
/**
* List of ignored id's
*/
this._ignoreInitialFocusList = [];
}
/**
* Get target HTML element to focus
*/
getFocusTarget() {
return this._focusTarget;
}
/**
* Set focus target if it's not alert close button
*/
setFocusTarget(target) {
if (!target.classList.contains('fudis-alert__close')) {
this._focusTarget = target;
}
}
/**
* Focus target element by id. If focusable element is not available when function is called, it
* will repeatively call itself until try counter is full.
*
* @param id Target to focus
* @param tryCounter How many times function should try to focus to given id
*/
focusToElementById(id, numberOfTries = 100) {
setTimeout(() => {
const element = this._document.getElementById(id);
if (element) {
element.focus();
}
else if (numberOfTries > 0) {
this.focusToElementById(id, numberOfTries - 1);
}
}, 50);
}
/**
* Add component with given id to ignore list, so that initialFocus is ignored with that. Used
* e.g. in a form where same component is added multiple times when user clicks 'New item' or
* similar and focus is wanted to move there except with 'item-id-x'.
*/
addToIgnoreList(id) {
if (!this._ignoreInitialFocusList.includes(id)) {
this._ignoreInitialFocusList.push(id);
}
}
/**
* Remove id from ignore initialFocus list
*/
removeFromIgnoreList(id) {
if (this._ignoreInitialFocusList.includes(id)) {
this._ignoreInitialFocusList = this._ignoreInitialFocusList.filter((item) => {
return item !== id;
});
}
}
/**
* Get list of ignored id's
*/
getIgnoreList() {
return this._ignoreInitialFocusList;
}
/**
* Check if current id is in the ignored list
*/
isIgnored(id) {
if (this._ignoreInitialFocusList.includes(id)) {
return true;
}
return false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisFocusService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisFocusService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisFocusService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }] });
class FudisDialogService {
constructor(ngMaterialDialog) {
this.ngMaterialDialog = ngMaterialDialog;
this._dialogOpen = new BehaviorSubject(false);
this._justClosedDropdownWithEscape = new BehaviorSubject(false);
}
/**
* Open new dialog.
*
* @param component Component or template to show in the dialog.
* @param config Optional configuration object. Use the `data` field to inject data into
* `component`.
* @returns Reference to the dialog that was opened.
*/
open(component, config) {
return this.ngMaterialDialog.open(component, FudisDialogService._createConfig(config));
}
/**
* Close opened Dialog
*
* @param dialogResult Data sent to Component which opened this dialog.
*/
close(dialogResult) {
const currentDialogs = this.ngMaterialDialog.openDialogs;
if (currentDialogs.length > 0) {
currentDialogs?.[currentDialogs.length - 1].close(dialogResult);
}
}
/**
* Close all instances of Dialogs
*/
closeAll() {
this.ngMaterialDialog.closeAll();
}
/**
* Get dialog open status
*/
getDialogOpenStatus() {
return this._dialogOpen;
}
/**
* Set dialog open
*/
setDialogOpenStatus(value) {
this._dialogOpen.next(value);
}
/**
* Set flag to indicate that a dropdown was just closed with Escape key, so that dialog does not
* also close with the same key press.
*/
dropdownClosedWithEscape() {
this._justClosedDropdownWithEscape.next(true); // set the flag
// Reset the flag after a short delay (backup)
setTimeout(() => {
this._justClosedDropdownWithEscape.next(false);
}, 1000);
}
/**
* Check if a dropdown was just closed with Escape key. Resets the flag after checking.
*/
hasJustClosedDropdownWithEscape() {
const hasJustClosed = this._justClosedDropdownWithEscape.value;
if (hasJustClosed) {
this._justClosedDropdownWithEscape.next(false); // Reset the flag
}
return hasJustClosed;
}
/**
* @returns Currently open Dialogs
*/
dialogsOpen() {
return this.ngMaterialDialog.openDialogs;
}
/**
* Merge consumer's config with ours.
*/
static _createConfig(userConfig = {}) {
const overridableOptions = {
hasBackdrop: true,
disableClose: true,
autoFocus: false,
ariaModal: true,
};
const forcedOptions = {
enterAnimationDuration: 0,
exitAnimationDuration: 0,
panelClass: 'fudis-dialog-panel',
backdropClass: 'fudis-dialog-backdrop',
};
return { ...overridableOptions, ...userConfig, ...forcedOptions };
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisDialogService, deps: [{ token: i1.MatDialog }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisDialogService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisDialogService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.MatDialog }] });
/**
* Displays a dismissible alert message.
*
* Use this component to show contextual feedback messages such as success, warning, or error state.
*/
class AlertComponent {
constructor(_document, _translateService, _alertService, _focusService, _dialogService) {
this._document = _document;
this._translateService = _translateService;
this._alertService = _alertService;
this._focusService = _focusService;
this._dialogService = _dialogService;
/**
* Variant of Alert: 'warning' | 'danger' | 'success' | 'info'. Same names and colors as in
* Notification Component.
*/
this.variant = 'info';
/**
* Output for close button click
*/
this.handleClose = new EventEmitter();
}
/**
* Handler for close button. Dismisses alert from service and sets focus to last alert in the list
* or to previously focused element stored with _handleFocus().
*/
_handleCloseClick(event) {
this._alertService.dismissAlertFromButton(this.buttonId);
const alerts = this._alertService.alerts.getValue();
if (alerts.length !== 0) {
const targetId = alerts[alerts.length - 1].buttonId;
this._document.getElementById(targetId)?.focus();
}
else {
const target = this._focusService.getFocusTarget();
target?.focus();
}
this.handleClose.emit(event);
}
/**
* Focus handler for close Button inside alert. Saves the element focus originated from, to
* restore focus there when there are no alerts left.
*/
_handleFocus(focusEvent) {
const relatedTarget = focusEvent?.relatedTarget;
const isDialogOpen = this._dialogService.getDialogOpenStatus().value;
/**
* First if: when keyboard tabbing through ngMaterial dialog, focus goes through its hidden
* focus-trap helper element which focuses on either first alert on the list or dialog close. So
* we store the dialog close as focus target. Else if: Store source of focus event unless it
* originated inside Alert
*/
if (isDialogOpen &&
relatedTarget &&
relatedTarget.classList?.contains('cdk-focus-trap-anchor')) {
const dialogCloseButton = this._document.querySelector('.cdk-focus-trap-anchor + mat-dialog-container .fudis-dialog__close .fudis-button');
if (dialogCloseButton) {
this._focusService.setFocusTarget(dialogCloseButton);
}
}
else if (relatedTarget && !relatedTarget.closest('.fudis-alert')) {
this._focusService.setFocusTarget(relatedTarget);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AlertComponent, deps: [{ token: DOCUMENT }, { token: FudisTranslationService }, { token: FudisAlertService }, { token: FudisFocusService }, { token: FudisDialogService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: AlertComponent, isStandalone: true, selector: "fudis-alert", inputs: { message: "message", htmlId: "htmlId", buttonId: "buttonId", variant: "variant" }, outputs: { handleClose: "handleClose" }, ngImport: i0, template: "<div class=\"fudis-alert fudis-alert__{{ variant }}\" [id]=\"htmlId\">\n <fudis-body-text\n [attr.role]=\"variant === 'danger' || variant === 'warning' ? 'alert' : 'status'\"\n [classList]=\"'fudis-alert__text'\"\n [align]=\"'center'\"\n [variant]=\"'lg-regular'\"\n >\n {{ message | async }}\n </fudis-body-text>\n\n <button\n [attr.aria-label]=\"\n (message | async) + ', ' + _translateService.getTranslations()().DIALOG.CLOSE\n \"\n [id]=\"buttonId\"\n (click)=\"_handleCloseClick($event)\"\n (focus)=\"_handleFocus($event)\"\n class=\"fudis-alert__close fudis-alert__close__{{ variant }}\"\n >\n <fudis-icon [icon]=\"'close'\" [color]=\"variant !== 'warning' ? 'white' : 'gray-dark'\" />\n </button>\n</div>\n", dependencies: [{ kind: "component", type: BodyTextComponent, selector: "fudis-body-text", inputs: ["variant", "align", "lang"] }, { kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AlertComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-alert', changeDetection: ChangeDetectionStrategy.OnPush, imports: [BodyTextComponent, IconComponent, AsyncPipe], template: "<div class=\"fudis-alert fudis-alert__{{ variant }}\" [id]=\"htmlId\">\n <fudis-body-text\n [attr.role]=\"variant === 'danger' || variant === 'warning' ? 'alert' : 'status'\"\n [classList]=\"'fudis-alert__text'\"\n [align]=\"'center'\"\n [variant]=\"'lg-regular'\"\n >\n {{ message | async }}\n </fudis-body-text>\n\n <button\n [attr.aria-label]=\"\n (message | async) + ', ' + _translateService.getTranslations()().DIALOG.CLOSE\n \"\n [id]=\"buttonId\"\n (click)=\"_handleCloseClick($event)\"\n (focus)=\"_handleFocus($event)\"\n class=\"fudis-alert__close fudis-alert__close__{{ variant }}\"\n >\n <fudis-icon [icon]=\"'close'\" [color]=\"variant !== 'warning' ? 'white' : 'gray-dark'\" />\n </button>\n</div>\n" }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: FudisTranslationService }, { type: FudisAlertService }, { type: FudisFocusService }, { type: FudisDialogService }], propDecorators: { message: [{
type: Input,
args: [{ required: true }]
}], htmlId: [{
type: Input,
args: [{ required: true }]
}], buttonId: [{
type: Input,
args: [{ required: true }]
}], variant: [{
type: Input
}], handleClose: [{
type: Output
}] } });
/**
* Groups and manages multiple alert messages.
*
* Use this component to display stacked alerts in a single, consistent location.
*/
class AlertGroupComponent {
constructor(_alertService, _translationService, _dialogService) {
this._alertService = _alertService;
this._translationService = _translationService;
this._dialogService = _dialogService;
/**
* CSS position of alerts: 'static' | 'absolute' | 'fixed'
*/
this.position = 'fixed';
/**
* List of Alerts fetched from Alert Service
*/
this._alertList = new BehaviorSubject([]);
/**
* Label for section element containing alerts
*/
this._alertGroupLabel = new BehaviorSubject('');
/**
* Boolean to determine if Alert group is visible
*/
this._visible = new BehaviorSubject(false);
this._dialogService
.getDialogOpenStatus()
.pipe(takeUntilDestroyed())
.subscribe((value) => {
this._setVisibility(value);
});
effect(() => {
this._alertGroupLabel.next(this._translationService.getTranslations()().ALERT.HEADING_LABEL);
});
}
ngOnChanges(changes) {
if (changes.insideDialog?.currentValue !== changes.insideDialog?.previousValue) {
this._setVisibility(this._dialogService.getDialogOpenStatus().value);
}
}
/**
* Get Alert Group's visible status
*/
getVisibleStatus() {
return this._visible.value;
}
/**
* Set visibility when Fudis Dialog is opened and closed
*/
_setVisibility(dialogStatus) {
if ((dialogStatus && this.insideDialog) || (!dialogStatus && !this.insideDialog)) {
this._visible.next(true);
}
else {
this._visible.next(false);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AlertGroupComponent, deps: [{ token: FudisAlertService }, { token: FudisTranslationService }, { token: FudisDialogService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: AlertGroupComponent, isStandalone: true, selector: "fudis-alert-group", inputs: { position: "position", insideDialog: "insideDialog" }, usesOnChanges: true, ngImport: i0, template: "@if ((_visible | async) && (_alertService.alerts | async); as alerts) {\n <section\n class=\"fudis-alert-group fudis-alert-group__{{ position }}\"\n [attr.aria-label]=\"alerts.length > 0 ? (_alertGroupLabel | async) + ': ' + alerts.length : null\"\n >\n @for (alert of alerts; track alert.id) {\n <fudis-alert\n [variant]=\"alert.type\"\n [message]=\"alert.message\"\n [id]=\"alert.id\"\n [htmlId]=\"alert.htmlId\"\n [buttonId]=\"alert.buttonId\"\n />\n }\n </section>\n}\n", styles: [".fudis-alert-group{z-index:300;top:0;left:0;width:100%}.fudis-alert-group__absolute{position:absolute}.fudis-alert-group__fixed{position:fixed}.fudis-alert-group__static{position:static}@media(orientation:landscape){.fudis-alert-group{width:100%}}@media(orientation:portrait){.fudis-alert-group{width:100vw}}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "fudis-alert", inputs: ["message", "htmlId", "buttonId", "variant"], outputs: ["handleClose"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: AlertGroupComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-alert-group', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AlertComponent, AsyncPipe], template: "@if ((_visible | async) && (_alertService.alerts | async); as alerts) {\n <section\n class=\"fudis-alert-group fudis-alert-group__{{ position }}\"\n [attr.aria-label]=\"alerts.length > 0 ? (_alertGroupLabel | async) + ': ' + alerts.length : null\"\n >\n @for (alert of alerts; track alert.id) {\n <fudis-alert\n [variant]=\"alert.type\"\n [message]=\"alert.message\"\n [id]=\"alert.id\"\n [htmlId]=\"alert.htmlId\"\n [buttonId]=\"alert.buttonId\"\n />\n }\n </section>\n}\n", styles: [".fudis-alert-group{z-index:300;top:0;left:0;width:100%}.fudis-alert-group__absolute{position:absolute}.fudis-alert-group__fixed{position:fixed}.fudis-alert-group__static{position:static}@media(orientation:landscape){.fudis-alert-group{width:100%}}@media(orientation:portrait){.fudis-alert-group{width:100vw}}\n"] }]
}], ctorParameters: () => [{ type: FudisAlertService }, { type: FudisTranslationService }, { type: FudisDialogService }], propDecorators: { position: [{
type: Input
}], insideDialog: [{
type: Input
}] } });
/**
* Displays a small status or category label.
*
* Use this component to convey concise information, like state or classification that supplements
* surrounding content.
*/
class BadgeComponent {
constructor() {
/**
* Badge variant: 'accent' | 'danger' | 'primary' | 'secondary' | 'success'
*/
this.variant = 'primary';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: BadgeComponent, isStandalone: true, selector: "fudis-badge", inputs: { variant: "variant", content: "content" }, ngImport: i0, template: "<span class=\"fudis-badge fudis-badge__{{ variant }}\">{{ content }}<ng-content></ng-content> </span>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: BadgeComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-badge', changeDetection: ChangeDetectionStrategy.OnPush, template: "<span class=\"fudis-badge fudis-badge__{{ variant }}\">{{ content }}<ng-content></ng-content> </span>\n" }]
}], propDecorators: { variant: [{
type: Input
}], content: [{
type: Input
}] } });
class PopoverApiDirective {
constructor() {
/**
* Position of the popover on the parent element: 'left' | 'right' | 'above' | 'below'
*/
this.popoverPosition = 'below';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PopoverApiDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: PopoverApiDirective, isStandalone: false, selector: "[fudisPopoverApi]", inputs: { popoverText: "popoverText", popoverPosition: "popoverPosition", popoverTriggerLabel: "popoverTriggerLabel" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PopoverApiDirective, decorators: [{
type: Directive,
args: [{
selector: '[fudisPopoverApi]',
standalone: false,
}]
}], propDecorators: { popoverText: [{
type: Input
}], popoverPosition: [{
type: Input
}], popoverTriggerLabel: [{
type: Input
}] } });
class ButtonBaseDirective extends PopoverApiDirective {
constructor(_idService) {
super();
this._idService = _idService;
/**
* Icon for button
*/
this.icon = undefined;
/**
* Icon rotation option: 'flip-180' | 'cw-90' | 'ccw-90' | 'none'
*/
this.iconRotate = 'none';
/**
* Button variant: 'primary' | 'secondary' | 'tertiary'
*/
this.variant = 'primary';
/**
* Disables the button, keeping it focusable for screen readers
*/
this.disabled = false;
/**
* Button size: 'extra-small' | 'small' | 'medium'
*/
this.size = 'medium';
/**
* Click handler
*/
this.handleClick = new EventEmitter();
/**
* Focus handler
*/
this.handleFocus = new EventEmitter();
/**
* Blur handler
*/
this.handleBlur = new EventEmitter();
/**
* OnDestroy handler emit
*/
this.handleDestroy = new EventEmitter();
/**
* Default Button size for classes
*/
this._size = 'medium';
/**
* Automatically sets icon color based on button variant
*/
this._iconColor = new BehaviorSubject('white');
/**
* Is button focused or not
*/
this._focused = false;
/**
* Button CSS class list
*/
this._classList = new BehaviorSubject([]);
}
ngOnInit() {
if (this.id) {
this._idService.addNewId('button', this.id);
}
else {
this.id = this._idService.getNewId('button');
}
if (this._classList.value.length === 0) {
this._classList.next(this._getClasses());
}
}
ngOnDestroy() {
this.handleDestroy.emit();
}
/**
* Handle button focus
*/
_handleButtonFocus(event) {
this._focused = true;
this.handleFocus.emit(event);
}
/**
* Determine icon color based on button variant. Return CSS classes with size and variant
*/
_getClasses() {
if (this.disabled) {
this._iconColor.next('gray-dark');
}
else if (this.variant === 'primary') {
this._iconColor.next('white');
}
else if (this.variant === 'secondary' || this.variant === 'tertiary') {
this._iconColor.next('primary');
}
return ['fudis-button', `fudis-button__size__${this._size}`, `fudis-button__${this.variant}`];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ButtonBaseDirective, deps: [{ token: FudisIdService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: ButtonBaseDirective, isStandalone: false, selector: "[fudisButtonBase]", inputs: { id: "id", icon: "icon", iconRotate: "iconRotate", variant: "variant", disabled: "disabled", size: "size" }, outputs: { handleClick: "handleClick", handleFocus: "handleFocus", handleBlur: "handleBlur", handleDestroy: "handleDestroy" }, viewQueries: [{ propertyName: "buttonEl", first: true, predicate: ["buttonElement"], descendants: true }], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ButtonBaseDirective, decorators: [{
type: Directive,
args: [{
selector: '[fudisButtonBase]',
standalone: false,
}]
}], ctorParameters: () => [{ type: FudisIdService }], propDecorators: { buttonEl: [{
type: ViewChild,
args: ['buttonElement']
}], id: [{
type: Input
}], icon: [{
type: Input
}], iconRotate: [{
type: Input
}], variant: [{
type: Input
}], disabled: [{
type: Input
}], size: [{
type: Input
}], handleClick: [{
type: Output
}], handleFocus: [{
type: Output
}], handleBlur: [{
type: Output
}], handleDestroy: [{
type: Output
}] } });
/**
* Displays additional content in an overlay container.
*/
class PopoverComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: PopoverComponent, isStandalone: true, selector: "fudis-popover", inputs: { id: "id", popoverText: "popoverText" }, ngImport: i0, template: "<div [id]=\"id\" aria-live=\"polite\" class=\"fudis-popover\">{{ popoverText }}</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PopoverComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-popover', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [id]=\"id\" aria-live=\"polite\" class=\"fudis-popover\">{{ popoverText }}</div>\n" }]
}], propDecorators: { id: [{
type: Input
}], popoverText: [{
type: Input
}] } });
/**
* Displays additional content in an overlay container.
*
* Use this directive to provide contextual information or tips without navigating away from the
* current view. Keep popover content concise.
*/
class PopoverDirective extends PopoverApiDirective {
constructor(_fudisIdService, _boundElement, _overlay) {
super();
this._fudisIdService = _fudisIdService;
this._boundElement = _boundElement;
this._overlay = _overlay;
this._destroyRef = inject(DestroyRef);
this._offset = 8;
this._overlayRef = null;
this._isPopoverOpen = false;
this._positions = {
above: {
originX: 'center',
originY: 'top',
overlayX: 'center',
overlayY: 'bottom',
offsetY: -this._offset,
},
left: {
originX: 'start',
originY: 'center',
overlayX: 'end',
overlayY: 'center',
offsetX: -this._offset,
},
right: {
originX: 'end',
originY: 'center',
overlayX: 'start',
overlayY: 'center',
offsetX: this._offset,
},
below: {
originX: 'center',
originY: 'bottom',
overlayX: 'center',
overlayY: 'top',
offsetY: this._offset,
},
};
}
ngOnInit() {
this._popoverElementId = this._fudisIdService.getNewId('popover');
this._boundElement?.nativeElement?.setAttribute('aria-controls', this._popoverElementId);
}
ngAfterViewInit() {
this._boundElement?.nativeElement?.addEventListener('click', this._clickHandler.bind(this));
this._boundElement?.nativeElement?.addEventListener('keydown', this._onEscPress.bind(this));
this._setAriaForBoundedElement('false');
}
_clickHandler() {
if (!this._isPopoverOpen) {
this._openPopover();
}
else {
this._closePopover();
}
}
_onEscPress(event) {
if (event.key === 'Escape')
this._closePopover();
}
_openPopover() {
const positionStrategy = this._overlay
.position()
.flexibleConnectedTo(this._boundElement)
.withPositions(this._getPositionStrategy())
.withPush(false)
.withScrollableContainers([]);
this._overlayRef = this._overlay.create({
positionStrategy,
scrollStrategy: this._overlay.scrollStrategies.close(),
});
this.clickSubscription = fromEvent(document, 'click')
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe((event) => this._focusOutHandler(event));
this.keydownSubscription = fromEvent(document, 'keyup')
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe((event) => this._focusOutHandler(event));
this.scrollSubscription = fromEvent(document, 'scroll', { capture: true })
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => this._scrollHandler());
const popoverPortal = new ComponentPortal(PopoverComponent);
const componentRef = this._overlayRef.attach(popoverPortal);
componentRef.instance.popoverText = this.popoverText;
componentRef.instance.id = this._popoverElementId;
this._isPopoverOpen = true;
this._setAriaForBoundedElement('true');
}
_focusOutHandler(event) {
if (this._overlayRef &&
!this._boundElement?.nativeElement?.contains(event?.target) &&
!document.getElementById(this._popoverElementId)?.contains(event?.target)) {
this._closePopover();
}
}
_scrollHandler() {
this._closePopover();
}
_closePopover() {
if (this._overlayRef) {
this._overlayRef.dispose();
this._overlayRef = null;
}
this.keydownSubscription?.unsubscribe();
this.clickSubscription?.unsubscribe();
this.scrollSubscription?.unsubscribe();
this._isPopoverOpen = false;
this._setAriaForBoundedElement('false');
}
_getPositionStrategy() {
const positions = [];
positions.push(this._positions[this.popoverPosition]);
for (const positionsKey in this._positions) {
if (positionsKey !== this.popoverPosition)
positions.push(this._positions[positionsKey]);
}
return positions;
}
_setAriaForBoundedElement(expanded) {
this._boundElement?.nativeElement?.setAttribute('aria-expanded', expanded);
if (expanded === 'true') {
this._boundElement.nativeElement.setAttribute('aria-describedby', this._popoverElementId);
}
else {
this._boundElement.nativeElement.removeAttribute('aria-describedby');
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PopoverDirective, deps: [{ token: FudisIdService }, { token: i0.ElementRef }, { token: i2.Overlay }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: PopoverDirective, isStandalone: true, selector: "[fudisPopover]", usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PopoverDirective, decorators: [{
type: Directive,
args: [{
selector: '[fudisPopover]',
}]
}], ctorParameters: () => [{ type: FudisIdService }, { type: i0.ElementRef }, { type: i2.Overlay }] });
/**
* Triggers an action or event.
*
* Use this component for primary user actions such as submitting forms, confirming choices, or
* navigating workflows.
*/
class ButtonComponent extends ButtonBaseDirective {
constructor(_idService) {
super(_idService);
/**
* Binding host CSS class to component wrapper
*/
this._classes = 'fudis-button-host';
/**
* Button size: 'small' | 'medium' ('extra-small' not available for Button, only IconButton)
*/
this.size = 'medium';
/**
* Button type: 'submit' | 'button'
*/
this.type = 'button';
}
ngOnChanges(changes) {
const variant = changes.variant?.currentValue !== changes.variant?.previousValue;
const disabled = changes.disabled?.currentValue !== changes.disabled?.previousValue;
const size = changes.size?.currentValue !== changes.size?.previousValue;
if (variant || disabled) {
this._classList.next(this._getClasses());
}
if (size) {
this._size = this.size;
this._classList.next(this._getClasses());
}
}
/**
* Button click event
*/
buttonClick(event) {
this.handleClick.emit(event);
}
/**
* Handler blurring out
*/
_handleButtonBlur(event) {
this._focused = false;
this.handleBlur.emit(event);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ButtonComponent, deps: [{ token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ButtonComponent, isStandalone: true, selector: "fudis-button", inputs: { label: "label", size: "size", type: "type" }, host: { properties: { "class": "this._classes" } }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "@if (popoverText) {\n <button\n fudisPopover\n #buttonElement\n [class]=\"_classList | async\"\n [type]=\"type\"\n (click)=\"!disabled && buttonClick($event)\"\n (blur)=\"!disabled && _handleButtonBlur($event)\"\n (focus)=\"!disabled && _handleButtonFocus($event)\"\n (keydown)=\"(!disabled)\"\n [attr.tabIndex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"disabled || null\"\n [attr.id]=\"id\"\n [popoverPosition]=\"popoverPosition\"\n [popoverText]=\"popoverText\"\n >\n @if (icon) {\n <div class=\"fudis-button__icon\">\n @if (_iconColor | async; as iconColor) {\n <fudis-icon\n class=\"fudis-button__icon__component\"\n [color]=\"iconColor\"\n [icon]=\"icon\"\n [rotate]=\"iconRotate\"\n ></fudis-icon>\n }\n </div>\n }\n {{ label }}\n </button>\n}\n\n@if (!popoverText) {\n <button\n #buttonElement\n [class]=\"_classList | async\"\n [type]=\"type\"\n (click)=\"!disabled && buttonClick($event)\"\n (blur)=\"!disabled && _handleButtonBlur($event)\"\n (focus)=\"!disabled && _handleButtonFocus($event)\"\n (keydown)=\"(!disabled)\"\n [attr.tabIndex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"disabled || null\"\n [attr.id]=\"id\"\n >\n @if (icon) {\n <div class=\"fudis-button__icon\">\n @if (_iconColor | async; as iconColor) {\n <fudis-icon\n class=\"fudis-button__icon__component\"\n [color]=\"iconColor\"\n [icon]=\"icon\"\n [rotate]=\"iconRotate\"\n ></fudis-icon>\n }\n </div>\n }\n {{ label }}</button\n >\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: PopoverDirective, selector: "[fudisPopover]" }, { kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ButtonComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-button', imports: [CommonModule, PopoverDirective, IconComponent], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (popoverText) {\n <button\n fudisPopover\n #buttonElement\n [class]=\"_classList | async\"\n [type]=\"type\"\n (click)=\"!disabled && buttonClick($event)\"\n (blur)=\"!disabled && _handleButtonBlur($event)\"\n (focus)=\"!disabled && _handleButtonFocus($event)\"\n (keydown)=\"(!disabled)\"\n [attr.tabIndex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"disabled || null\"\n [attr.id]=\"id\"\n [popoverPosition]=\"popoverPosition\"\n [popoverText]=\"popoverText\"\n >\n @if (icon) {\n <div class=\"fudis-button__icon\">\n @if (_iconColor | async; as iconColor) {\n <fudis-icon\n class=\"fudis-button__icon__component\"\n [color]=\"iconColor\"\n [icon]=\"icon\"\n [rotate]=\"iconRotate\"\n ></fudis-icon>\n }\n </div>\n }\n {{ label }}\n </button>\n}\n\n@if (!popoverText) {\n <button\n #buttonElement\n [class]=\"_classList | async\"\n [type]=\"type\"\n (click)=\"!disabled && buttonClick($event)\"\n (blur)=\"!disabled && _handleButtonBlur($event)\"\n (focus)=\"!disabled && _handleButtonFocus($event)\"\n (keydown)=\"(!disabled)\"\n [attr.tabIndex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"disabled || null\"\n [attr.id]=\"id\"\n >\n @if (icon) {\n <div class=\"fudis-button__icon\">\n @if (_iconColor | async; as iconColor) {\n <fudis-icon\n class=\"fudis-button__icon__component\"\n [color]=\"iconColor\"\n [icon]=\"icon\"\n [rotate]=\"iconRotate\"\n ></fudis-icon>\n }\n </div>\n }\n {{ label }}</button\n >\n}\n" }]
}], ctorParameters: () => [{ type: FudisIdService }], propDecorators: { _classes: [{
type: HostBinding,
args: ['class']
}], label: [{
type: Input,
args: [{ required: true }]
}], size: [{
type: Input
}], type: [{
type: Input
}] } });
/**
* All of these functions are used to check if component's control has different validators. The
* return value of these functions is usually assigned for internal variable to sort out wanted
* logic.
*/
const FudisValidatorUtilities = {
required: required$1,
oneRequiredOrMin,
maxLength: maxLength$1,
minLength: minLength$1,
max: max$2,
min: min$2,
minDate,
maxDate,
};
/**
* @param control
* @returns If provided FormControl has 'required' validator
*/
function required$1(control) {
const nativeRequired = control.hasValidator(Validators.required);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fudisRequired = !!control.validator?.('');
if (nativeRequired || fudisRequired) {
return true;
}
return false;
}
/**
* @param group FormGroup
* @returns If provided FormGroup has oneRequired or min validator
*/
function oneRequiredOrMin(group) {
const validatorFn = group.validator;
if (validatorFn === null) {
return false;
}
const errors = validatorFn(new FormGroup({
controlAsNull: new FormControl(null),
controlAsFalse: new FormControl(false),
controlAsEmptyString: new FormControl(''),
}));
const hasProperErrors = !!(errors?.['oneRequired'] || errors?.['min'].value > 0);
return hasProperErrors;
}
/**
* @param control
* @returns If provided FormControl has max length value binded to its validators
*/
function maxLength$1(control) {
const validatorFn = control.validator;
if (validatorFn === null) {
return null;
}
const errors = validatorFn(new FormControl({ length: Infinity }));
return errors?.['maxlength']?.requiredLength;
}
/**
* @param control
* @returns If provided FormControl has min length value binded to its validators
*/
function minLength$1(control) {
const validatorFn = control.validator;
if (validatorFn === null) {
return null;
}
const errors = validatorFn(new FormControl({ length: -Infinity }));
return errors?.['minlength']?.requiredLength;
}
/**
* @param control
* @returns If provided FormControl has max value binded to its validators
*/
function max$2(control) {
const validatorFn = control.validator;
if (validatorFn === null) {
return null;
}
const errors = validatorFn(new FormControl(Infinity)) ?? {};
return errors?.['max']?.max;
}
/**
* @param control
* @returns If provided FormControl has min value binded to its validators
*/
function min$2(control) {
const validatorFn = control.validator;
if (validatorFn === null) {
return null;
}
const errors = validatorFn(new FormControl(-Infinity)) ?? {};
return errors?.['min']?.min;
}
/**
* @param control
* @returns If provided FormControl has datepickerMin value binded to its validators
*/
function minDate(control) {
const validatorFn = control.validator;
if (validatorFn === null) {
return undefined;
}
const errors = validatorFn(new FormControl(new Date(1337, 0, 1)));
const value = errors?.['datepickerMin']?.value;
if (value) {
const timezone = value.getTimezoneOffset() * 60000;
const returnValue = new Date(value.getTime() - timezone);
return returnValue;
}
return undefined;
}
/**
* @param control
* @returns If provided FormControl has datepickerMax value binded to its validators
*/
function maxDate(control) {
const validatorFn = control.validator;
if (validatorFn === null) {
return undefined;
}
const errors = validatorFn(new FormControl(new Date(4242, 0, 1)));
const value = errors?.['datepickerMax']?.value;
if (value) {
const timezone = value.getTimezoneOffset() * 60000;
const returnValue = new Date(value.getTime() - timezone);
return returnValue;
}
return undefined;
}
/**
* Represents a single boolean form control.
*
* Use this component when user need to toggle an option on or off.
*/
class CheckboxComponent {
constructor(_idService, _translationService) {
this._idService = _idService;
this._translationService = _translationService;
/**
* Emits Checkbox change
*/
this.handleChange = new EventEmitter();
/**
* Trigger update when control validator is changed
*/
this._updateValueAndValidityTrigger = new Subject();
/**
* Set requiredText based on this boolean value
*/
this._required = new BehaviorSubject(false);
this._destroyRef = inject(DestroyRef);
/**
* If Checkbox has focus
*/
this._focused = false;
/**
* Signals reflecting the current state of the form control, updated on every control event.
*/
this._touched = signal(false, ...(ngDevMode ? [{ debugName: "_touched" }] : /* istanbul ignore next */ []));
this._invalid = signal(false, ...(ngDevMode ? [{ debugName: "_invalid" }] : /* istanbul ignore next */ []));
this._disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
this._value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
this._updateValueAndValidityTrigger.pipe(takeUntilDestroyed()).subscribe(() => {
if (this.control) {
this._required.next(FudisValidatorUtilities.required(this.control));
}
});
}
/**
* Copy FormControl states into local signals so the template tracks them reactively without
* requiring markForCheck().
*/
_syncControlState() {
this._touched.set(this.control.touched);
this._invalid.set(this.control.invalid);
this._disabled.set(this.control.disabled);
this._value.set(this.control.value);
}
/**
* When Checkbox is focused
*/
_onFocus() {
this._focused = true;
}
/**
* When Checkbox is blurred
*/
_onBlur() {
this._focused = false;
}
/**
* Manual toggle for checkbox checked state
*/
_onChange(event) {
const input = event.target;
const newValue = input.checked ? true : null;
this.control.setValue(newValue);
this.control.markAsTouched();
this.control.updateValueAndValidity();
const optionToEmit = {
id: this.id,
label: this.label,
value: newValue,
};
// Emit after control updates
this.handleChange.emit({ checkbox: optionToEmit, control: this.control });
}
ngOnInit() {
if (this.id) {
this._idService.addNewId('checkbox', this.id);
}
else {
this.id = this._idService.getNewId('checkbox');
}
this._updateValueAndValidityTrigger.next();
}
/**
* Sync local signals with FormControl states and add value and validity check when control value
* changes.
*/
ngOnChanges(changes) {
if (changes.control?.currentValue !== changes.control?.previousValue) {
this._syncControlState();
this._subscription?.unsubscribe();
this._subscription = this.control.events
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => {
this._syncControlState();
this._updateValueAndValidityTrigger.next();
});
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CheckboxComponent, deps: [{ token: FudisIdService }, { token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: CheckboxComponent, isStandalone: true, selector: "fudis-checkbox", inputs: { control: "control", id: "id", label: "label", ariaLabelledBy: "ariaLabelledBy", ariaDescribedBy: "ariaDescribedBy" }, outputs: { handleChange: "handleChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"fudis-checkbox\">\n <label\n [attr.for]=\"id\"\n class=\"fudis-checkbox__label\"\n [class.fudis-checkbox__label--disabled]=\"_disabled()\"\n >\n <input\n type=\"checkbox\"\n class=\"fudis-checkbox__input\"\n [id]=\"id\"\n [formControl]=\"control\"\n [checked]=\"_value()\"\n [attr.aria-disabled]=\"_disabled()\"\n [attr.aria-required]=\"_required | async\"\n [attr.aria-describedby]=\"ariaDescribedBy\"\n [attr.aria-labelledby]=\"ariaLabelledBy\"\n [attr.tabindex]=\"_disabled() ? -1 : null\"\n [attr.aria-invalid]=\"(_touched() && _invalid()) || null\"\n (focus)=\"_onFocus()\"\n (blur)=\"_onBlur()\"\n (change)=\"_onChange($event)\"\n />\n <div class=\"fudis-checkbox__content\" [class.fudis-checkbox__content__disabled]=\"_disabled()\">\n <div class=\"fudis-checkbox__content-wrapper\">\n <span\n class=\"fudis-checkbox__content__box\"\n [class.fudis-checkbox__content__box--focused]=\"_focused\"\n [class.fudis-checkbox__content__box--invalid]=\"(_touched() && _invalid()) || null\"\n [class.fudis-checkbox__content__box--disabled]=\"_disabled()\"\n >\n @if (_value()) {\n <fudis-icon [icon]=\"'check'\" class=\"fudis-checkbox__content__box__icon\" />\n }\n </span>\n </div>\n </div>\n @if (label) {\n <span class=\"fudis-checkbox__label__text\">{{ label }}</span>\n }\n </label>\n <ng-content></ng-content>\n @if (this._required | async) {\n <span class=\"fudis-checkbox__required\"\n >({{ _translationService.getTranslations()().REQUIRED }})</span\n >\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CheckboxComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-checkbox', imports: [FormsModule, ReactiveFormsModule, IconComponent, AsyncPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fudis-checkbox\">\n <label\n [attr.for]=\"id\"\n class=\"fudis-checkbox__label\"\n [class.fudis-checkbox__label--disabled]=\"_disabled()\"\n >\n <input\n type=\"checkbox\"\n class=\"fudis-checkbox__input\"\n [id]=\"id\"\n [formControl]=\"control\"\n [checked]=\"_value()\"\n [attr.aria-disabled]=\"_disabled()\"\n [attr.aria-required]=\"_required | async\"\n [attr.aria-describedby]=\"ariaDescribedBy\"\n [attr.aria-labelledby]=\"ariaLabelledBy\"\n [attr.tabindex]=\"_disabled() ? -1 : null\"\n [attr.aria-invalid]=\"(_touched() && _invalid()) || null\"\n (focus)=\"_onFocus()\"\n (blur)=\"_onBlur()\"\n (change)=\"_onChange($event)\"\n />\n <div class=\"fudis-checkbox__content\" [class.fudis-checkbox__content__disabled]=\"_disabled()\">\n <div class=\"fudis-checkbox__content-wrapper\">\n <span\n class=\"fudis-checkbox__content__box\"\n [class.fudis-checkbox__content__box--focused]=\"_focused\"\n [class.fudis-checkbox__content__box--invalid]=\"(_touched() && _invalid()) || null\"\n [class.fudis-checkbox__content__box--disabled]=\"_disabled()\"\n >\n @if (_value()) {\n <fudis-icon [icon]=\"'check'\" class=\"fudis-checkbox__content__box__icon\" />\n }\n </span>\n </div>\n </div>\n @if (label) {\n <span class=\"fudis-checkbox__label__text\">{{ label }}</span>\n }\n </label>\n <ng-content></ng-content>\n @if (this._required | async) {\n <span class=\"fudis-checkbox__required\"\n >({{ _translationService.getTranslations()().REQUIRED }})</span\n >\n }\n</div>\n" }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: FudisTranslationService }], propDecorators: { control: [{
type: Input,
args: [{ required: true }]
}], id: [{
type: Input
}], label: [{
type: Input
}], ariaLabelledBy: [{
type: Input
}], ariaDescribedBy: [{
type: Input
}], handleChange: [{
type: Output
}] } });
class FormCommonApiDirective extends PopoverApiDirective {
constructor(_idService, _focusService) {
super();
this._idService = _idService;
this._focusService = _focusService;
/**
* If component is a child of Form component, Form's Error Summary is visible, this component's
* control has errors and when this component is loaded for the first time, it will by default
* call Error Summary to reload itself again and mark control as touched. This is because if
* component is lazy loaded to the DOM after the initial reload errors call was made, errors of
* this component might not appear on the list. To disable this feature, set this to false.
*/
this.errorSummaryReloadOnInit = true;
/**
* Set browser focus to the input on the first load.
*/
this.initialFocus = false;
/**
* Preferred way to disable input is to set formControl as disabled. This Input property should be
* used in edge cases only.
*/
this.disabled = false;
/**
* To listen for component's blur event.
*/
this.handleBlur = new EventEmitter();
/**
* To listen for component's focus event.
*/
this.handleFocus = new EventEmitter();
/**
* To listen for input's key up event.
*/
this.handleKeyUp = new EventEmitter();
/**
* Output emitted when component has finished AfterViewInit lifecycle hook
*/
this.handleViewInit = new EventEmitter();
/**
* Set requiredText based on this boolean value
*/
this._required = new BehaviorSubject(false);
this._destroyRef = inject(DestroyRef);
/**
* Trigger update when control validator is changed
*/
this._updateValueAndValidityTrigger = new Subject();
/**
* Used prevent checks in HTML DOM before content has been loaded
*/
this._afterViewInitDone = signal(false, ...(ngDevMode ? [{ debugName: "_afterViewInitDone" }] : /* istanbul ignore next */ []));
}
/**
* Generate id for parent component
*/
_setParentComponentId(parentType) {
if (this.id) {
this._idService.addNewParentId(parentType, this.id);
}
else {
this.id = this._idService.getNewParentId(parentType);
}
}
/**
* Add given id to Id Service or generate unique id
*/
_setComponentId(componentType) {
if (this.id) {
this._idService.addNewId(componentType, this.id);
}
else {
this.id = this._idService.getNewId(componentType);
}
}
_afterViewInitCommon() {
if (this.initialFocus && !this._focusService.isIgnored(this.id)) {
this.focusToInput();
}
this._afterViewInitDone.set(true);
this.handleViewInit.emit();
}
ngAfterViewInit() {
this._afterViewInitCommon();
}
/**
* Set focus to the input element
*/
focusToInput() {
this._inputRef?.nativeElement?.focus();
}
/**
* Executed when component's input is focused or when child input, e.g. Checkbox is focused
*/
onFocus(event) {
this.handleFocus.emit(event);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormCommonApiDirective, deps: [{ token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: FormCommonApiDirective, isStandalone: true, selector: "[fudisFormCommonApi]", inputs: { label: "label", ariaLabel: "ariaLabel", id: "id", errorSummaryReloadOnInit: "errorSummaryReloadOnInit", helpText: "helpText", initialFocus: "initialFocus", disabled: "disabled", disableGuidance: "disableGuidance" }, outputs: { handleBlur: "handleBlur", handleFocus: "handleFocus", handleKeyUp: "handleKeyUp", handleViewInit: "handleViewInit" }, viewQueries: [{ propertyName: "_inputRef", first: true, predicate: ["inputRef"], descendants: true }], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormCommonApiDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisFormCommonApi]' }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: FudisFocusService }], propDecorators: { _inputRef: [{
type: ViewChild,
args: ['inputRef']
}], label: [{
type: Input,
args: [{ required: true }]
}], ariaLabel: [{
type: Input
}], id: [{
type: Input
}], errorSummaryReloadOnInit: [{
type: Input
}], helpText: [{
type: Input
}], initialFocus: [{
type: Input
}], disabled: [{
type: Input
}], disableGuidance: [{
type: Input
}], handleBlur: [{
type: Output
}], handleFocus: [{
type: Output
}], handleKeyUp: [{
type: Output
}], handleViewInit: [{
type: Output
}] } });
class GroupComponentBaseDirective extends FormCommonApiDirective {
constructor(_idService, _focusService) {
super(_idService, _focusService);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GroupComponentBaseDirective, deps: [{ token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: GroupComponentBaseDirective, isStandalone: true, selector: "[fudisGroupComponentBase]", inputs: { formGroup: "formGroup" }, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GroupComponentBaseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisGroupComponentBase]' }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: FudisFocusService }], propDecorators: { formGroup: [{
type: Input
}] } });
/**
* Internal Error Summary tools not exposed to public
*/
class FudisInternalErrorSummaryService {
constructor(_translationService) {
this._translationService = _translationService;
// --------------------
//
// CLASS VARIABLES
//
// --------------------
/**
* Collection of all registered errors categorised by parent Form id. Used as "temporary" storage
* and value will be passed to Observable when reloadFormErrors is called.
*/
this._errorsStore = {};
/**
* Collection of all registered errors categorised by parent Form id. This Observable is updated
* with new value only when reloadFormErrors is called.
*/
this._errorsObservable = new BehaviorSubject({});
this._errorsSignal = {};
/**
* Current Form ids with their Error Summary Visibility status
*/
this._errorSummaryVisibilityStatus = {};
/**
* Collection of child Sections, Expandables and Fieldsets of each Form Component
*/
this._formStructure = {};
/**
* Info to Error Summary Component if it should move user focus to updated list or not
*/
this._focusToFormOnReload = null;
/**
* Update strategy of Error Summary
*/
this._updateStrategy = 'reloadOnly';
/**
* Used prevent unnecessary frequent Signal updates
*/
this._reloadGuard = [];
/**
* Signal for counting submit attempts
*/
this._submitAttempt = signal(0, ...(ngDevMode ? [{ debugName: "_submitAttempt" }] : /* istanbul ignore next */ []));
toObservable(_translationService.getLanguageSignal())
.pipe(takeUntilDestroyed())
.subscribe(() => {
this.focusToFormOnReload = null;
});
}
// --------------------
//
// GETTERS & SETTERS FOR CLASS VARIABLES
//
// --------------------
/**
* Used in Components to listen to Reload updates
*/
get errorsObservable() {
return this._errorsObservable;
}
/**
* For unit testing purposes
*/
get errors() {
return this._errorsStore;
}
/**
* For unit testing purposes
*/
get errorsSignal() {
return this._errorsSignal;
}
/**
* Used for firing focus to error summary when submitting
*/
get submitAttempt() {
return this._submitAttempt;
}
/**
* Observable to store each Form's Error Summary's visibility status. Form component will listen
* to these changes if visiblity changes elsewhere than the Input() prop
*/
get errorSummaryVisibilityStatus() {
return this._errorSummaryVisibilityStatus;
}
get updateStrategy() {
return this._updateStrategy;
}
set updateStrategy(value) {
this._updateStrategy = value;
}
get focusToFormOnReload() {
return this._focusToFormOnReload;
}
get formStructure() {
return this._formStructure;
}
set focusToFormOnReload(value) {
this._focusToFormOnReload = value;
}
/**
* Hide or show Error Summary of specific Form Component
*
* @param formId Form to target
* @param visible Hide or show Error Summary
*/
setErrorSummaryVisibility(formId, visible) {
if (!this._errorSummaryVisibilityStatus[formId]) {
this._errorSummaryVisibilityStatus[formId] = signal(visible);
}
else if (this._errorSummaryVisibilityStatus[formId]() !== visible) {
this._errorSummaryVisibilityStatus[formId].set(visible);
}
}
// --------------------
//
// ADD AND REMOVE ERRORS
//
// --------------------
/**
* Adds a new error to the list of current errors If new error item has a matching id on the list,
* new error is tied to that error list object
*
* @param newError Form error summary item
*/
addError(newError) {
if (!this._errorsStore[newError.formId]) {
this.registerNewForm(newError.formId);
}
const currentErrors = { ...this._errorsStore?.[newError.formId] };
if (!currentErrors?.[newError.focusId]) {
currentErrors[newError.focusId] = {};
}
const currentMessage = currentErrors[newError.focusId][newError.id];
const messageChanged = currentMessage && currentMessage !== newError.message;
const contentChanged = currentErrors?.[newError.focusId] && messageChanged;
currentErrors[newError.focusId][newError.id] = newError.message;
this._errorsStore[newError.formId] = currentErrors;
const reloadErrors = (contentChanged || this._updateStrategy === 'all') &&
!!this._errorSummaryVisibilityStatus?.[newError.formId]();
if (reloadErrors) {
this._focusToFormOnReload = null;
this.reloadFormErrors(newError.formId, false);
}
}
/**
* Removes error object from the current errors list if it contains matching error id
*
* @param error Error object
*/
removeError(errorToRemove) {
const currentErrorsOfForm = { ...this._errorsStore[errorToRemove.formId] };
if (currentErrorsOfForm[errorToRemove.focusId]?.[errorToRemove.id]) {
delete currentErrorsOfForm[errorToRemove.focusId][errorToRemove.id];
const otherErrors = Object.keys(currentErrorsOfForm[errorToRemove.focusId]).length;
if (otherErrors === 0) {
delete currentErrorsOfForm[errorToRemove.focusId];
}
this._errorsStore[errorToRemove.formId] = currentErrorsOfForm;
const reloadErrors = (this._updateStrategy === 'onRemove' || this._updateStrategy === 'all') &&
!!this._errorSummaryVisibilityStatus?.[errorToRemove.formId]();
if (reloadErrors) {
this._focusToFormOnReload = null;
this.reloadFormErrors(errorToRemove.formId);
}
}
}
// --------------------
//
// RELOADING LIST OF ERRORS TO HTML DOM
//
// Functions to actually update Components which listen to Error changes. These will update the
// Observables components are listening to.
//
// --------------------
/**
* Updates the visible and dynamic lists of all form and errors with the current error list
*/
reloadAllErrors() {
Object.keys(this._errorsStore).forEach((key) => {
if (this._errorSummaryVisibilityStatus[key]()) {
this.reloadFormErrors(key, false);
}
});
}
/**
* @param formId
* @param focus
*/
reloadFormErrors(formId, focus) {
if (focus) {
this._focusToFormOnReload = formId;
this._submitAttempt.update((submitCount) => submitCount + 1);
}
else {
this._focusToFormOnReload = null;
}
const reloadConditions = !this._reloadGuard.includes(formId) &&
this._errorsStore[formId] &&
this._errorsSignal[formId];
if (reloadConditions) {
this._reloadGuard.push(formId);
setTimeout(() => {
this._errorsSignal[formId]?.set(this._errorsStore[formId]);
this._errorsObservable.next({ ...this._errorsStore });
this._reloadGuard.splice(this._reloadGuard.indexOf(formId), 1);
}, 50);
}
}
// --------------------
//
// DOM RELATED FUNCTIONS
//
// Usually called by Components when they are initialized and they register themselves to Error
// Summary Service
//
// --------------------
/**
* @param element HTMLElement to check, if it has Form Component as ancestor
* @returns If ancestor found, returns id of that Form and visibility status of Form's Error
* Summary
*/
getFormAncestorId(element) {
let foundId = null;
let tryCounter = 0;
return new Promise((resolve) => {
const interval = setInterval(() => {
Object.keys(this._errorSummaryVisibilityStatus).find((id) => {
if (element.closest(`#${id}`)) {
foundId = id;
}
});
if (foundId) {
clearInterval(interval);
resolve(foundId);
}
else if (tryCounter < 50) {
tryCounter = tryCounter + 1;
}
else {
clearInterval(interval);
resolve(null);
}
}, 10);
});
}
/**
* When new Form Component is initialized, it will register itself for child components and error
* messages related to it
*
* @param formId
*/
registerNewForm(formId, errorSummaryVisible = false) {
if (!this._formStructure[formId]) {
this._formStructure[formId] = {
sections: {},
fieldsets: {},
};
}
if (!this._errorSummaryVisibilityStatus[formId]) {
this._errorSummaryVisibilityStatus[formId] = signal(errorSummaryVisible);
}
if (!this._errorsSignal[formId]) {
this._errorsSignal[formId] = signal({});
}
if (!this._errorsStore[formId]) {
this._errorsStore[formId] = {};
}
}
/**
* When Form Component is destroyed, it will remove itself from the service
*
* @param formId
*/
removeForm(formId) {
if (this._errorsStore[formId]) {
delete this._errorsStore[formId];
}
if (this._errorsSignal[formId]) {
delete this._errorsSignal[formId];
}
if (this._formStructure[formId]) {
delete this._formStructure[formId];
}
if (this._errorSummaryVisibilityStatus[formId]) {
delete this._errorSummaryVisibilityStatus[formId];
}
}
/**
* Adds new fieldset to the list of current fieldsets If a fieldset with a matching id exists,
* replace the old fieldset with the new one
*
* @param fieldset Form error summary fieldset
*/
addFieldset(fieldset) {
if (this._formStructure?.[fieldset.formId]) {
this._formStructure[fieldset.formId].fieldsets[fieldset.id] = fieldset.title;
}
}
/**
* Removes the fieldset from the current fieldsets
*
* @param fieldset Form error summary fieldset
*/
removeFieldset(formId, fieldsetId) {
delete this._formStructure?.[formId]?.fieldsets[fieldsetId];
}
/**
* Adds new section to the list of current sections If a section with a matching id exists,
* replace the old section with the new one
*
* @param section Form error summary section
*/
addSection(section) {
if (this._formStructure?.[section.formId]) {
this._formStructure[section.formId].sections[section.id] = section.title;
}
}
/**
* Removes the section from the current sections
*
* @param section Form error summary section
*/
removeSection(formId, sectionId) {
delete this._formStructure?.[formId]?.sections[sectionId];
}
// --------------------
//
// MISC
//
// --------------------
ngOnDestroy() {
this._errorsObservable.complete();
}
/**
* Returns an error id including a control name if one is given
*
* @param id Id of the form error summary item
* @param controlName Control name of the form error summary item
*/
defineErrorId(id, controlName) {
return controlName ? `${id}_${controlName}` : id;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisInternalErrorSummaryService, deps: [{ token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisInternalErrorSummaryService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisInternalErrorSummaryService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: FudisTranslationService }] });
class ValidatorErrorMessageComponent {
constructor(_errorSummaryService, _idService) {
this._errorSummaryService = _errorSummaryService;
this._idService = _idService;
/**
* Is error visible or not
*/
this.visible = false;
/**
* Name of control this error is related to.
*/
this.controlName = undefined;
/**
* Visual variant of error message
*/
this.variant = 'form-error';
/**
* Output for handling a state when error is sent to Error Summary
*/
this.handleCreateError = new EventEmitter();
/**
* Output for handling a state when error is removed from Error Summary
*/
this.handleRemoveError = new EventEmitter();
/**
* Error message to include in error summary item
*/
this._currentMessage = new BehaviorSubject('');
/**
* Has error been created and sent forward
*/
this._errorSent = false;
this._id = _idService.getNewId('validator-error-message');
}
_subscribeToMessage(message) {
this._messageSubscribtion = message.subscribe((value) => {
if (this._currentMessage.value !== value) {
this._currentMessage.next(value);
this._createError();
}
});
}
ngAfterViewInit() {
setTimeout(() => {
if (typeof this.message !== 'string' && !this.message) {
this.throwError();
}
}, 1000);
}
ngOnChanges(changes) {
if (changes.message?.currentValue !== changes.message?.previousValue ||
changes.label?.currentValue !== changes.label?.previousValue ||
changes.formId?.currentValue !== changes.formId?.previousValue) {
const newMessage = changes.message?.currentValue;
const newLabel = changes.label?.currentValue;
const newFormId = changes.formId?.currentValue;
if (newMessage) {
if (typeof newMessage === 'string') {
this._currentMessage.next(newMessage);
this._createError();
}
else {
this._subscribeToMessage(newMessage);
}
}
if (newLabel) {
this._createError();
}
if (newFormId) {
this._createError();
}
}
}
ngOnDestroy() {
this._removeError();
if (this._messageSubscribtion) {
this._messageSubscribtion.unsubscribe();
}
}
throwError() {
if (this.controlName) {
console.warn(`Fudis component with id of '${this.focusId}' and control name of '${this.controlName}' is missing error message for error type of: '${this.type}'`);
}
else {
console.warn(`Fudis component with id of '${this.focusId}' is missing error message for error type of: '${this.type}'`);
}
}
/**
* Create new error to be sent to Error Summary
*/
_createError() {
if (this.formId && this.focusId && this._currentMessage.value) {
const newError = {
focusId: this.focusId,
formId: this.formId,
message: `${this.label}: ${this._currentMessage.value}`,
id: this.controlName ? `${this.type}_${this.controlName}` : this.type,
};
this._errorSummaryService.addError(newError);
this._errorSent = true;
// Emit in a microtask only after the current change detection cycle.
// This ensures that the OnPush components using Guidance detects the updated state.
// Emitting synchronously from ngOnChanges would otherwise break change detection, and in some cases cause NG0100 errors.
queueMicrotask(() => {
this.handleCreateError.emit(newError);
});
}
}
/**
* Remove existing error from Error Summary
*/
_removeError() {
if (this._errorSent && this.formId) {
const errorToRemove = {
focusId: this.focusId,
formId: this.formId,
id: this.controlName ? `${this.type}_${this.controlName}` : this.type,
};
this._errorSummaryService.removeError(errorToRemove);
this.handleRemoveError.emit(errorToRemove);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ValidatorErrorMessageComponent, deps: [{ token: FudisInternalErrorSummaryService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ValidatorErrorMessageComponent, isStandalone: true, selector: "fudis-validator-error-message", inputs: { focusId: "focusId", label: "label", type: "type", message: "message", visible: "visible", controlName: "controlName", variant: "variant", formId: "formId" }, outputs: { handleCreateError: "handleCreateError", handleRemoveError: "handleRemoveError" }, usesOnChanges: true, ngImport: i0, template: "@if (visible && (_currentMessage | async); as message) {\n <p [id]=\"_id\" class=\"fudis-error-message fudis-error-message__{{ variant }}\">{{ message }}</p>\n}\n", styles: [".fudis-error-message{margin-top:calc(.5rem / var(--fudis-rem-multiplier));margin-bottom:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-error-message__body-text{text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400}.fudis-error-message__form-error{color:#b83c2e;text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ValidatorErrorMessageComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-validator-error-message', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AsyncPipe], template: "@if (visible && (_currentMessage | async); as message) {\n <p [id]=\"_id\" class=\"fudis-error-message fudis-error-message__{{ variant }}\">{{ message }}</p>\n}\n", styles: [".fudis-error-message{margin-top:calc(.5rem / var(--fudis-rem-multiplier));margin-bottom:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-error-message__body-text{text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400}.fudis-error-message__form-error{color:#b83c2e;text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400}\n"] }]
}], ctorParameters: () => [{ type: FudisInternalErrorSummaryService }, { type: FudisIdService }], propDecorators: { focusId: [{
type: Input,
args: [{ required: true }]
}], label: [{
type: Input,
args: [{ required: true }]
}], type: [{
type: Input,
args: [{ required: true }]
}], message: [{
type: Input,
args: [{ required: true }]
}], visible: [{
type: Input
}], controlName: [{
type: Input
}], variant: [{
type: Input
}], formId: [{
type: Input
}], handleCreateError: [{
type: Output
}], handleRemoveError: [{
type: Output
}] } });
class GuidanceComponent {
constructor(_translationService, _idService, _errorSummaryService, _elementRef) {
this._translationService = _translationService;
this._idService = _idService;
this._errorSummaryService = _errorSummaryService;
this._elementRef = _elementRef;
/**
* When set displays also a character count indicator.
*/
this.maxLength = null;
/**
* Used with together with Checkbox Group component, to display errors only when focus has moved
* outside of whole Checkbox Group.
*/
this.groupBlurredOut = true;
/**
* Hide visible helptext from screen readers when group components E.g. Radio Button Group and
* Checkbox Group components are used.
*/
this.groupHelpTextHidden = false;
/**
* Assistive text of max character count for screen readers. E. g. "5/20 characters used" where
* "characters used" is "maxLengthText".
*/
this._maxLengthText = signal('', ...(ngDevMode ? [{ debugName: "_maxLengthText" }] : /* istanbul ignore next */ []));
/**
* Collection of validation errors
*/
this._lazyLoadedErrors = [];
this._parentFormId = signal(null, ...(ngDevMode ? [{ debugName: "_parentFormId" }] : /* istanbul ignore next */ []));
/**
* Used prevent checks in HTML DOM before content has been loaded
*/
this._afterViewInitDone = signal(false, ...(ngDevMode ? [{ debugName: "_afterViewInitDone" }] : /* istanbul ignore next */ []));
/**
* To prevent Error Summary reloading after lazy load check
*/
this._reloadGuard = false;
this._cdr = inject(ChangeDetectorRef);
this._destroyRef = inject(DestroyRef);
this._injector = inject(Injector);
this._id = _idService.getNewId('guidance');
effect(() => {
this._maxLengthText.set(_translationService.getTranslations()().TEXTINPUT.MAX_LENGTH);
});
}
ngOnInit() {
this._setCharacterLimitIndicatorValues();
}
ngOnChanges(changes) {
if (changes.maxLength?.currentValue !== changes.maxLength?.previousValue) {
this._setCharacterLimitIndicatorValues();
}
if (changes.control) {
this.control?.events
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => this._cdr.markForCheck());
}
if (changes.formGroup) {
this.formGroup?.events
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => this._cdr.markForCheck());
}
}
ngAfterContentInit() {
this._errorSummaryService
.getFormAncestorId(this._elementRef.nativeElement)
.then((formParentId) => {
if (formParentId) {
this._parentFormId.set(formParentId);
this._subscribeToErrors(formParentId);
}
else {
this._parentFormId.set(null);
}
});
}
ngAfterViewInit() {
this._afterViewInitDone.set(true);
setTimeout(() => {
this._reloadGuard = true;
}, 100);
}
_subscribeToErrors(formId) {
/**
* If there's a function call of errorSummaryService.reloadFormErrors('id-of-this-form'), and
* this Guidance's parent Form id is that 'id-for-this-form', this effect() check will trigger
* and set this Guidance's control / group as touched, so possible errors are set as visible.
*/
toObservable(this._errorSummaryService.errorsSignal[formId], {
injector: this._injector,
}).subscribe((errors) => {
let errorsFound = !!errors?.[this.for];
// With FormGroups errors are defined differently, so this checks if FormGroups control has errors
if (this.formGroup && !errorsFound) {
errorsFound = Object.keys(this.formGroup.controls).some((controlName) => {
const errorId = this._errorSummaryService.defineErrorId(this.for, controlName);
return !!errors?.[errorId];
});
}
if (errorsFound) {
if (this.control?.invalid) {
this.control.markAsTouched();
}
else if (this.formGroup?.invalid) {
this.formGroup.markAllAsTouched();
}
}
});
}
_setCharacterLimitIndicatorValues() {
if (this.maxLength) {
this._maxLengthWidth = this.maxLength >= 100 ? 'lg' : this.maxLength >= 10 ? 'md' : 'sm';
this._maxLengthAlertThreshold = this.maxLength - 5;
}
}
/**
* This function is triggered, if this component is loaded to the DOM after Error Summary has been
* loaded and there are new validation errors which didn't exist at the time original reload
* errors call was made. It should only trigger reload one time, when all errors of this Guidance
* are registered.
*/
_reloadErrorSummaryOnLazyLoad(error) {
if (this._parentFormId() &&
this._errorSummaryService.errorSummaryVisibilityStatus[this._parentFormId()]() &&
!this._lazyLoadedErrors.includes(error.id)) {
this._lazyLoadedErrors.push(error.id);
this._errorSummaryService.focusToFormOnReload = null;
let numberOfErrors = 0;
if (this.control?.errors) {
numberOfErrors = numberOfErrors + Object.keys(this.control.errors).length;
}
if (this.formGroup) {
if (this.formGroup.errors) {
numberOfErrors = numberOfErrors + Object.keys(this.formGroup.errors).length;
}
Object.keys(this.formGroup.controls).forEach((control) => {
const controlErrors = this.formGroup.controls[control]?.errors;
if (controlErrors) {
numberOfErrors = numberOfErrors + Object.keys(controlErrors).length;
}
});
}
if (numberOfErrors === this._lazyLoadedErrors.length && !this._reloadGuard) {
/**
* NOTE: This function currently runs before _subscribeToErrors which is not how it ideally
* should be working (or was initially intended). The triggering happens because
* handleCreateError Output is emitted from child component during the current change
* detection. It is now deferred by using queueMicrotask, which runs after the current
* change detection cycle, to ensure that OnPush components can detect changes.
*/
this._errorSummaryService.reloadFormErrors(this._parentFormId(), false);
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GuidanceComponent, deps: [{ token: FudisTranslationService }, { token: FudisIdService }, { token: FudisInternalErrorSummaryService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GuidanceComponent, isStandalone: true, selector: "fudis-guidance", inputs: { for: "for", inputLabel: "inputLabel", control: "control", formGroup: "formGroup", helpText: "helpText", maxLength: "maxLength", selectedOption: "selectedOption", groupBlurredOut: "groupBlurredOut", groupHelpTextHidden: "groupHelpTextHidden" }, usesOnChanges: true, ngImport: i0, template: "@let parentFormId = _parentFormId();\n\n<div class=\"fudis-guidance\" [id]=\"_id\">\n <div [id]=\"for + '_guidance'\">\n <div\n [id]=\"_id + '-errors'\"\n [class.fudis-guidance__errors]=\"\n _afterViewInitDone() &&\n ((control && control.touched) || (formGroup && formGroup.touched && groupBlurredOut))\n \"\n >\n @if (\n (control?.invalid && control?.touched) ||\n (formGroup?.invalid && formGroup?.touched && groupBlurredOut)\n ) {\n <fudis-icon [icon]=\"'alert-fill'\" />\n }\n <div class=\"fudis-guidance__errors__list\">\n <!--\n Check errors for each control FormGroup e. g. english, finnish, swedish\n -->\n @if (formGroup?.controls && !control && !formGroup?.errors) {\n @for (singleControl of formGroup?.controls | keyvalue; track singleControl.key) {\n @if (singleControl.value.errors) {\n @for (error of singleControl.value.errors | keyvalue; track error.key) {\n @if (error.value.message) {\n <fudis-validator-error-message\n (handleCreateError)=\"_reloadErrorSummaryOnLazyLoad($event)\"\n [formId]=\"parentFormId\"\n [focusId]=\"for\"\n [visible]=\"\n !!((singleControl.value.touched || formGroup.touched) && groupBlurredOut)\n \"\n [type]=\"error.key\"\n [controlName]=\"singleControl.key\"\n [label]=\"inputLabel\"\n [message]=\"error.value.message\"\n />\n }\n }\n }\n }\n }\n\n <!--\n Check parent level errors of FormGroup e. g. oneRequired\n -->\n @if (formGroup?.errors && formGroup?.invalid) {\n @for (error of formGroup?.errors | keyvalue; track error.key) {\n @if (error.value?.message) {\n <fudis-validator-error-message\n (handleCreateError)=\"_reloadErrorSummaryOnLazyLoad($event)\"\n [formId]=\"parentFormId\"\n [visible]=\"!!(formGroup.touched && groupBlurredOut)\"\n [focusId]=\"for\"\n [type]=\"error.key\"\n [label]=\"inputLabel\"\n [message]=\"error.value?.message\"\n />\n }\n }\n }\n\n <!--\n Check errors for single FormControl e. g. single text-input with maxlength error\n -->\n @if (control?.errors && !formGroup) {\n @for (error of control.errors | keyvalue; track error.key) {\n @if (error.value?.message) {\n <fudis-validator-error-message\n (handleCreateError)=\"_reloadErrorSummaryOnLazyLoad($event)\"\n [formId]=\"parentFormId\"\n [visible]=\"control.touched\"\n [focusId]=\"for\"\n [type]=\"error.key\"\n [label]=\"inputLabel\"\n [message]=\"error.value?.message\"\n />\n }\n }\n }\n <ng-content />\n </div>\n </div>\n\n @if (helpText || (control && maxLength)) {\n <p [attr.aria-hidden]=\"groupHelpTextHidden\" class=\"fudis-guidance__help-text\">\n @if (helpText) {\n {{ helpText }}\n }\n </p>\n }\n </div>\n @if (maxLength && control) {\n <small\n class=\"fudis-guidance__character-limit-indicator fudis-guidance__character-limit-indicator__{{\n _maxLengthWidth\n }}\"\n [class.fudis-guidance__character-limit-indicator__float-right]=\"!helpText\"\n >\n {{ control.value?.length || 0 }}/{{ maxLength }}\n <span class=\"fudis-visually-hidden\">{{ _maxLengthText() }}</span>\n </small>\n }\n @if (formGroup?.controls && !control && maxLength) {\n @for (singleControl of formGroup?.controls | keyvalue; track singleControl.key) {\n @if (singleControl.key === selectedOption) {\n <small\n class=\"fudis-guidance__character-limit-indicator fudis-guidance__character-limit-indicator__{{\n _maxLengthWidth\n }}\"\n [class.fudis-guidance__character-limit-indicator__float-right]=\"!helpText\"\n >\n {{ singleControl.value.value?.length || 0 }}/{{ maxLength }}\n <span class=\"fudis-visually-hidden\">{{ _maxLengthText() }}</span>\n </small>\n }\n @if (\n singleControl.value.value?.length === _maxLengthAlertThreshold ||\n singleControl.value.value?.length === maxLength\n ) {\n <p\n class=\"fudis-guidance__character-limit-indicator__alert fudis-visually-hidden\"\n role=\"alert\"\n >\n {{ singleControl.value.value?.length || 0 }}/{{ maxLength }} {{ _maxLengthText() }}</p\n >\n }\n }\n }\n</div>\n@if (\n control &&\n maxLength &&\n (control.value?.length === _maxLengthAlertThreshold || control.value?.length === maxLength)\n) {\n <p class=\"fudis-guidance__character-limit-indicator__alert fudis-visually-hidden\" role=\"alert\"\n >{{ control.value?.length || 0 }}/{{ maxLength }} {{ _maxLengthText() }}</p\n >\n}\n", styles: [".fudis-guidance__errors--visible{display:flex;transform:translate(calc(-1 * .5rem / var(--fudis-rem-multiplier)));opacity:1;height:auto}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }, { kind: "component", type: ValidatorErrorMessageComponent, selector: "fudis-validator-error-message", inputs: ["focusId", "label", "type", "message", "visible", "controlName", "variant", "formId"], outputs: ["handleCreateError", "handleRemoveError"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GuidanceComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-guidance', imports: [IconComponent, ValidatorErrorMessageComponent, KeyValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let parentFormId = _parentFormId();\n\n<div class=\"fudis-guidance\" [id]=\"_id\">\n <div [id]=\"for + '_guidance'\">\n <div\n [id]=\"_id + '-errors'\"\n [class.fudis-guidance__errors]=\"\n _afterViewInitDone() &&\n ((control && control.touched) || (formGroup && formGroup.touched && groupBlurredOut))\n \"\n >\n @if (\n (control?.invalid && control?.touched) ||\n (formGroup?.invalid && formGroup?.touched && groupBlurredOut)\n ) {\n <fudis-icon [icon]=\"'alert-fill'\" />\n }\n <div class=\"fudis-guidance__errors__list\">\n <!--\n Check errors for each control FormGroup e. g. english, finnish, swedish\n -->\n @if (formGroup?.controls && !control && !formGroup?.errors) {\n @for (singleControl of formGroup?.controls | keyvalue; track singleControl.key) {\n @if (singleControl.value.errors) {\n @for (error of singleControl.value.errors | keyvalue; track error.key) {\n @if (error.value.message) {\n <fudis-validator-error-message\n (handleCreateError)=\"_reloadErrorSummaryOnLazyLoad($event)\"\n [formId]=\"parentFormId\"\n [focusId]=\"for\"\n [visible]=\"\n !!((singleControl.value.touched || formGroup.touched) && groupBlurredOut)\n \"\n [type]=\"error.key\"\n [controlName]=\"singleControl.key\"\n [label]=\"inputLabel\"\n [message]=\"error.value.message\"\n />\n }\n }\n }\n }\n }\n\n <!--\n Check parent level errors of FormGroup e. g. oneRequired\n -->\n @if (formGroup?.errors && formGroup?.invalid) {\n @for (error of formGroup?.errors | keyvalue; track error.key) {\n @if (error.value?.message) {\n <fudis-validator-error-message\n (handleCreateError)=\"_reloadErrorSummaryOnLazyLoad($event)\"\n [formId]=\"parentFormId\"\n [visible]=\"!!(formGroup.touched && groupBlurredOut)\"\n [focusId]=\"for\"\n [type]=\"error.key\"\n [label]=\"inputLabel\"\n [message]=\"error.value?.message\"\n />\n }\n }\n }\n\n <!--\n Check errors for single FormControl e. g. single text-input with maxlength error\n -->\n @if (control?.errors && !formGroup) {\n @for (error of control.errors | keyvalue; track error.key) {\n @if (error.value?.message) {\n <fudis-validator-error-message\n (handleCreateError)=\"_reloadErrorSummaryOnLazyLoad($event)\"\n [formId]=\"parentFormId\"\n [visible]=\"control.touched\"\n [focusId]=\"for\"\n [type]=\"error.key\"\n [label]=\"inputLabel\"\n [message]=\"error.value?.message\"\n />\n }\n }\n }\n <ng-content />\n </div>\n </div>\n\n @if (helpText || (control && maxLength)) {\n <p [attr.aria-hidden]=\"groupHelpTextHidden\" class=\"fudis-guidance__help-text\">\n @if (helpText) {\n {{ helpText }}\n }\n </p>\n }\n </div>\n @if (maxLength && control) {\n <small\n class=\"fudis-guidance__character-limit-indicator fudis-guidance__character-limit-indicator__{{\n _maxLengthWidth\n }}\"\n [class.fudis-guidance__character-limit-indicator__float-right]=\"!helpText\"\n >\n {{ control.value?.length || 0 }}/{{ maxLength }}\n <span class=\"fudis-visually-hidden\">{{ _maxLengthText() }}</span>\n </small>\n }\n @if (formGroup?.controls && !control && maxLength) {\n @for (singleControl of formGroup?.controls | keyvalue; track singleControl.key) {\n @if (singleControl.key === selectedOption) {\n <small\n class=\"fudis-guidance__character-limit-indicator fudis-guidance__character-limit-indicator__{{\n _maxLengthWidth\n }}\"\n [class.fudis-guidance__character-limit-indicator__float-right]=\"!helpText\"\n >\n {{ singleControl.value.value?.length || 0 }}/{{ maxLength }}\n <span class=\"fudis-visually-hidden\">{{ _maxLengthText() }}</span>\n </small>\n }\n @if (\n singleControl.value.value?.length === _maxLengthAlertThreshold ||\n singleControl.value.value?.length === maxLength\n ) {\n <p\n class=\"fudis-guidance__character-limit-indicator__alert fudis-visually-hidden\"\n role=\"alert\"\n >\n {{ singleControl.value.value?.length || 0 }}/{{ maxLength }} {{ _maxLengthText() }}</p\n >\n }\n }\n }\n</div>\n@if (\n control &&\n maxLength &&\n (control.value?.length === _maxLengthAlertThreshold || control.value?.length === maxLength)\n) {\n <p class=\"fudis-guidance__character-limit-indicator__alert fudis-visually-hidden\" role=\"alert\"\n >{{ control.value?.length || 0 }}/{{ maxLength }} {{ _maxLengthText() }}</p\n >\n}\n", styles: [".fudis-guidance__errors--visible{display:flex;transform:translate(calc(-1 * .5rem / var(--fudis-rem-multiplier)));opacity:1;height:auto}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: FudisIdService }, { type: FudisInternalErrorSummaryService }, { type: i0.ElementRef }], propDecorators: { for: [{
type: Input,
args: [{ required: true }]
}], inputLabel: [{
type: Input,
args: [{ required: true }]
}], control: [{
type: Input
}], formGroup: [{
type: Input
}], helpText: [{
type: Input
}], maxLength: [{
type: Input
}], selectedOption: [{
type: Input
}], groupBlurredOut: [{
type: Input
}], groupHelpTextHidden: [{
type: Input
}] } });
/**
* Utility function used with GridDirective. Takes in object of Input() attributes and creates CSS
* classlist out of those to be passed to Grid element.
*/
const getGridClasses = (values) => {
const classList = [
'fudis-grid',
`fudis-grid__${values.width}`,
`fudis-grid__align__${values.align}`,
values.rowGap === 'responsive' ? '' : `fudis-grid__row-gap__${values.rowGap}`,
values.columnGap === 'responsive' ? '' : `fudis-grid__column-gap__${values.columnGap}`,
];
if (values.classes) {
classList.push(values.classes);
}
return classList
.filter((item) => {
return !!item;
})
.join(' ');
};
const replaceFormInputWidthsToRem = (value) => {
const convertMap = {
inputXs: 'calc(4rem / var(--fudis-rem-multiplier))',
inputSm: 'calc(10rem / var(--fudis-rem-multiplier))',
inputMd: 'calc(14rem / var(--fudis-rem-multiplier))',
inputLg: 'calc(23rem / var(--fudis-rem-multiplier))',
};
const valueArray = value.split(' ');
valueArray.forEach((value, index) => {
valueArray[index] = convertMap?.[value] ? convertMap[value] : value;
});
return valueArray.join(' ');
};
/**
* Utility function to convert parameters to either Grid CSS value 'grid-column-template' or CSS
* grid item value 'grid-column'
*/
const getGridCssValue = (value, isGridItem) => {
if (typeof value === 'number') {
if (!isGridItem) {
return `repeat(${value}, 1fr)`;
}
return `span ${value}`;
}
if (value === 'stretch' && isGridItem) {
return '1/-1';
}
if (value !== 'start' && value !== 'stretch' && value !== 'end' && value !== 'center') {
return replaceFormInputWidthsToRem(value);
}
return value;
};
/**
* Determine Grid Input Properties based on if Service defaults should be ignored or if application
* has provided values
*/
const getValuesForCSSClasses = (properties, serviceDefaults) => {
const { defaultValues, serviceValues, appValues } = properties;
if (!serviceDefaults) {
/**
* Set values from Inputs, if not provided, use default values. Ignore default values from
* FudisGridService
*/
return {
width: appValues.width ?? defaultValues.width,
align: appValues.align ?? defaultValues.align,
alignItemsX: appValues.alignItemsX ?? defaultValues.alignItemsX,
alignItemsY: appValues.alignItemsY ?? defaultValues.alignItemsY,
rowGap: appValues.rowGap ?? defaultValues.rowGap,
columnGap: appValues.columnGap ?? defaultValues.columnGap,
classes: appValues.classes ?? defaultValues.classes,
};
}
else {
/**
* Set values from Inputs, if not provided, check if FudisGridService has defaults, if not, use
* defaults.
*/
return {
width: appValues.width ?? serviceValues.width ?? defaultValues.width,
align: appValues.align ?? serviceValues.align ?? defaultValues.align,
alignItemsX: appValues.alignItemsX ?? serviceValues.alignItemsX ?? defaultValues.alignItemsX,
alignItemsY: appValues.alignItemsY ?? serviceValues.alignItemsY ?? defaultValues.alignItemsY,
rowGap: appValues.rowGap ?? serviceValues.rowGap ?? defaultValues.rowGap,
columnGap: appValues.columnGap ?? serviceValues.columnGap ?? defaultValues.columnGap,
classes: appValues.classes ?? serviceValues.classes ?? defaultValues.classes,
};
}
};
class GridApiDirective {
constructor() {
/**
* Alignment of Grid component inside its parent: 'start' | 'end' | 'center'
*/
this.align = 'start';
/**
* Horizontal alignment of Grid Items in a row: 'start' | 'center' | 'end' | 'stretch' |
* 'baseline'
*/
this.alignItemsX = 'stretch';
/**
* Vertical alignment of Grid Items in a row: 'start' | 'center' | 'end' | 'stretch' | 'baseline'
*/
this.alignItemsY = 'stretch';
/**
* Gutter gap between columns: 'none' | 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' |
* 'responsive'
*/
this.columnGap = 'responsive';
/**
* Setting of columns for the Grid. Input will be converted to native CSS grid
* grid-template-columns values.
*/
this.columns = '1fr';
/**
* To make Grid ignore default values defined by application and FudisGridService
*/
this.serviceDefaults = true;
/**
* Grid row gap: 'none' | 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'responsive'
*/
this.rowGap = 'none';
/**
* Maximum width of Grid: 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs' | 'initial' | 'full-width'.
* When viewport gets narrower, grid automatically adjusts to lower sizes, except in full-width
* mode.
*/
this.width = 'xxl';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridApiDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: GridApiDirective, isStandalone: true, selector: "[fudisGridApi]", inputs: { align: "align", alignItemsX: "alignItemsX", alignItemsY: "alignItemsY", classes: "classes", columnGap: "columnGap", columns: "columns", serviceDefaults: "serviceDefaults", rowGap: "rowGap", width: "width" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridApiDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisGridApi]' }]
}], propDecorators: { align: [{
type: Input
}], alignItemsX: [{
type: Input
}], alignItemsY: [{
type: Input
}], classes: [{
type: Input
}], columnGap: [{
type: Input
}], columns: [{
type: Input
}], serviceDefaults: [{
type: Input
}], rowGap: [{
type: Input
}], width: [{
type: Input
}] } });
// --------------------
//
// Grid related types
//
// --------------------
/**
* Default 'columns' value for Grid component
*/
const gridColumnDefault = '1fr';
const gridInputPropertyDefaults = {
align: 'start',
alignItemsX: 'stretch',
alignItemsY: 'stretch',
classes: undefined,
columnGap: 'responsive',
rowGap: 'responsive',
width: 'xxl',
};
/**
* Grid widths
*/
const fudisGridWidthArray = [
'xxl',
'xl',
'lg',
'md',
'sm',
'xs',
'initial',
'full-width',
];
/**
* Alignment of whole Grid inside its container. For items inside the Grid use FudisGridAlignItems.
*/
const fudisGridAlignArray = ['start', 'end', 'center'];
/**
* Alignment of Grid Items inside a Grid
*/
const fudisGridAlignItemsArray = ['start', 'center', 'end', 'stretch', 'baseline'];
// --------------------
//
// Grid Item related types
//
// --------------------
/**
* Default value for 'columns' input of Grid Item
*/
const gridItemDefault = 'auto';
/**
* Utility object used with breakpointsMinWidthToObserve and BreakpointObserver
*/
const fudisBreakpointsMinWidth = {
xxl: '(min-width: 100em)',
xl: '(min-width: 75em)',
lg: '(min-width: 62em)',
md: '(min-width: 48em)',
sm: '(min-width: 36em)',
xs: '(min-width: 0)',
default: '(min-width: 0)',
};
/*
* Array of breakpoint rules to observe, which is given to ngMaterial BreakpointObserver
*/
const breakpointsMinWidthToObserve = [
fudisBreakpointsMinWidth.xxl,
fudisBreakpointsMinWidth.xl,
fudisBreakpointsMinWidth.lg,
fudisBreakpointsMinWidth.md,
fudisBreakpointsMinWidth.sm,
fudisBreakpointsMinWidth.xs,
fudisBreakpointsMinWidth.default,
];
const fudisSpacingArray = ['none', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
/**
* Default spacing value if none is given
*/
const defaultSpacingValue = '0';
/**
* Fudis spacing tokens converted to rem values
*/
const fudisSpacingValues = {
xxs: 'calc(0.25rem / var(--fudis-rem-multiplier))',
xs: 'calc(0.5rem / var(--fudis-rem-multiplier))',
sm: 'calc(1rem / var(--fudis-rem-multiplier))',
md: 'calc(1.5rem / var(--fudis-rem-multiplier))',
lg: 'calc(2rem / var(--fudis-rem-multiplier))',
xl: 'calc(2.5rem / var(--fudis-rem-multiplier))',
xxl: 'calc(4rem / var(--fudis-rem-multiplier))',
none: '0',
default: defaultSpacingValue,
};
/**
* Returns an array of FudisBreakpointStyleResponsive objects that applies FudisSpacingValues for a
* given breakpoints. Determines if breakpoint data consists FudisSpacing values, fractions or
* numbers. If fractions or numbers are used, calls grid utility function that returns native grid
* CSS layout properties as a value.
*/
const getBreakpointRules = (values, defaultValue, isGridItem) => {
const valueArray = [];
/**
* If application values do not contain default value, assign defaultValue from Grid components
* gridColumnDefault type.
*/
if (!values.default) {
valueArray.push({
name: 'default',
value: defaultValue,
breakpoint: fudisBreakpointsMinWidth.default,
});
}
Object.keys(values).forEach((key) => {
const value = values[key];
const spacings = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
// TODO: This if-else logic could probably be improved/refactored?
// Check if value includes FudisSpacing tokens...
if (spacings.includes(value)) {
valueArray.push({
name: key,
value: fudisSpacingValues[value],
breakpoint: fudisBreakpointsMinWidth[key],
});
}
// ...else call grid utility function.
// Grid columns can be set by fractions or by plain numbers so the array push should not get mixed up with spacings.
// Returns e. g. following object:
// [
// {
// "name": "default",
// "value": "1fr",
// "breakpoint": "(min-width: 0)"
// },
// {
// "name": "md",
// "value": "1fr 2fr",
// "breakpoint": "(min-width: 48em)"
// },
// {
// "name": "xxl",
// "value": "repeat(6, 1fr)",
// "breakpoint": "(min-width: 100em)"
// }
// ]
else {
const valueToForward = getGridCssValue(value, isGridItem);
valueArray.push({
name: key,
value: valueToForward,
breakpoint: fudisBreakpointsMinWidth[key],
});
}
});
return valueArray;
};
/**
* Builds, sorts and validates breakpoint data
*/
const getBreakpointDataArray = (value, defaultValue, isGridItem) => {
const valueArray = getBreakpointRules(value, defaultValue, isGridItem);
const sortOrder = ['default', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
const sortedValueArray = valueArray.sort((a, b) => {
return sortOrder.indexOf(a.name) - sortOrder.indexOf(b.name);
});
return sortedValueArray;
};
class FudisGridService {
constructor() {
/**
* Grid values that can be set from application. By default an empty object.
*/
this._defaultGridValues = signal({}, ...(ngDevMode ? [{ debugName: "_defaultGridValues" }] : /* istanbul ignore next */ []));
}
/**
* To set default values for all Grids application uses from application.
*/
setDefaultValues(newDefaultValues) {
let checkedNewValues = {};
Object.keys(newDefaultValues).forEach((key) => {
const keyName = key;
const newValue = newDefaultValues[keyName];
if (newValue) {
checkedNewValues = { ...checkedNewValues, [keyName]: newValue };
}
else {
throw new Error(`FudisGridService.setDefaultValues() received on object with key of: '${keyName}' and value of: '${newValue}'. This value was not applied to default values.`);
}
});
this._defaultGridValues.set({ ...this._defaultGridValues(), ...checkedNewValues });
}
/**
* Get application's default values for Grid
*/
getDefaultValues() {
return this._defaultGridValues.asReadonly();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisGridService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisGridService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisGridService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
class FudisBreakpointService {
/**
* Observe breakpoints and when hitting one, save results to Signal.
*/
constructor(fudisBreakpointObserver) {
/**
* Current screen size/breakpoint with true/false state
*/
this._currentScreenSize = signal(null, ...(ngDevMode ? [{ debugName: "_currentScreenSize" }] : /* istanbul ignore next */ []));
/**
* Readonly value for current screen size and state
*/
this._screenSize = this._currentScreenSize.asReadonly();
fudisBreakpointObserver
.observe(breakpointsMinWidthToObserve)
.pipe(takeUntilDestroyed())
.subscribe((state) => {
this._currentScreenSize.set(state);
});
}
/**
* Get current state of Breakpoints
*/
getBreakpointState() {
return this._screenSize();
}
/**
* Function to apply CSS properties
*/
setStyleAttributes(element, property, value) {
const elementToModify = element;
const previousStyles = elementToModify.getAttribute('style');
if (typeof value === 'string') {
elementToModify.style.cssText = `
${previousStyles};
${property}: ${value === 'none' ? '0' : value};
`;
}
else {
value?.forEach((item) => {
if (this._screenSize()?.breakpoints[item.breakpoint]) {
elementToModify.style.cssText = `
${previousStyles};
${property}: ${item.value === 'none' ? '0' : item.value};
`;
}
});
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisBreakpointService, deps: [{ token: i1$1.BreakpointObserver }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisBreakpointService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisBreakpointService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1$1.BreakpointObserver }] });
/**
* Applies grid layout behavior to a container element.
*
* Use this directive to define responsive columns.
*/
class GridDirective extends GridApiDirective {
constructor(_gridElement, _gridService, _breakpointService) {
super();
this._gridElement = _gridElement;
this._gridService = _gridService;
this._breakpointService = _breakpointService;
/**
* Used to apply grid-template-columns CSS values for the Grid
*/
this._calculatedColumns = gridColumnDefault;
/**
* Object to store various values from defaults, GridService and Inputs from application
*/
this._gridInputProperties = {
appValues: {},
defaultValues: {},
serviceValues: {},
};
this._firstLoadFinished = false;
this._element = this._gridElement.nativeElement;
/**
* Set default values to property collection object
*/
this._setDefaultValuesToPropertyObject();
/**
* When screen is resized, check and apply new rules for Grid columns
*/
effect(() => {
_breakpointService.getBreakpointState();
if (typeof this._calculatedColumns !== 'string' &&
typeof this._calculatedColumns !== 'number') {
this._setColumnsForBreakpoints(this._calculatedColumns);
}
});
/**
* Update columns and CSS classes, if GridService values update
*/
effect(() => {
/**
* Get GridServices values
*/
this._gridInputProperties.serviceValues = this._gridService.getDefaultValues()();
/**
* Re-run Columns and CSS class calculations if GridService's values udpate
*/
if (this._firstLoadFinished) {
this._calculateAndSetGridColumnsCss();
this._setAllProperties();
}
});
}
ngOnInit() {
this._firstLoadFinished = true;
this._calculateAndSetGridColumnsCss();
this._setAllProperties();
}
ngOnChanges(changes) {
/**
* From changed values, update property object
*/
this._setAppInputValuesToPropertyObject(changes);
if (this._firstLoadFinished) {
if (changes.columns?.currentValue) {
this._calculateAndSetGridColumnsCss();
}
this._setAllProperties();
}
}
/**
* Add or update values from application. This does not apply any values for CSS.
*/
_setAppInputValuesToPropertyObject(changes) {
const keysToDismiss = ['serviceDefaults', 'ngOnInit', 'ngOnChanges'];
Object.keys(changes).forEach((key) => {
if (!keysToDismiss.includes(key)) {
const keyToUse = key;
const newValue = changes[keyToUse].currentValue;
this._gridInputProperties.appValues = {
...this._gridInputProperties.appValues,
[keyToUse]: newValue,
};
}
});
}
/**
* Set default values. These are used as back up, if application does not provide any input
* properties.
*/
_setDefaultValuesToPropertyObject() {
this._gridInputProperties.defaultValues = gridInputPropertyDefaults;
}
/* ---------------------------------------------
*
* Class functions for defining Grid's 'grid-template-columns' CSS value
*
* ---------------------------------------------
*/
/**
* Set and config Grid's attributes from provided input properties
*/
_calculateAndSetGridColumnsCss() {
this._calculatedColumns = this._calculateColumnsCssValue();
this._setColumnsForBreakpoints(this._calculatedColumns);
}
/**
* Function which converts application's input or service values to CSS values which will applied
* to Grid element.
*/
_calculateColumnsCssValue() {
const columnsToApply = this._gridInputProperties.appValues?.columns;
const columnsFromService = this.serviceDefaults && this._gridInputProperties.serviceValues?.columns
? this._gridInputProperties.serviceValues?.columns
: undefined;
if (typeof columnsToApply === 'string') {
return replaceFormInputWidthsToRem(columnsToApply);
}
// If value is number, convert it to grid-template-column value. E. g. number 6 converts to 'repeat(6,1fr)'
if (typeof columnsToApply === 'number') {
return getGridCssValue(columnsToApply);
}
let combinedValues;
if (columnsFromService && columnsToApply) {
combinedValues = { ...columnsFromService, ...columnsToApply };
return getBreakpointDataArray(combinedValues, gridColumnDefault);
}
if (columnsToApply) {
return getBreakpointDataArray(columnsToApply, gridColumnDefault);
}
if (columnsFromService) {
return getBreakpointDataArray(columnsFromService, gridColumnDefault);
}
return gridColumnDefault;
}
/**
* Set CSS grid-template-column attributes for this Grid element
*/
_setColumnsForBreakpoints(columnsCssValue) {
this._breakpointService.setStyleAttributes(this._element, 'grid-template-columns', columnsCssValue);
}
/* ---------------------------------------------
*
* Class functions for defining Grid's other than 'grid-template-columns' CSS values
*
* ---------------------------------------------
*/
_setAllProperties() {
this._valuesForCssClasses = getValuesForCSSClasses(this._gridInputProperties, this.serviceDefaults);
this._element.style.justifyItems = this._valuesForCssClasses.alignItemsX;
this._element.style.alignItems = this._valuesForCssClasses.alignItemsY;
this._element.classList.value = getGridClasses(this._valuesForCssClasses);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridDirective, deps: [{ token: i0.ElementRef }, { token: FudisGridService }, { token: FudisBreakpointService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: GridDirective, isStandalone: true, selector: "[fudisGrid]", usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisGrid]' }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: FudisGridService }, { type: FudisBreakpointService }] });
/**
* Dropdown event service shared between Icon Button and DropdownMenu components.
*
* Dropdown menu element's width calculation must be triggered from host Icon Button click, and it
* should ensure that the dropdown is always fully visible.
*/
class DropdownEventService {
constructor() {
this._triggerCalculationSubject = new Subject();
this.triggerCalculation = this._triggerCalculationSubject.asObservable();
}
triggerWidthCalculation() {
this._triggerCalculationSubject.next();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownEventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownEventService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownEventService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* Button represented primarily by an icon.
*
* Use this component for compact actions where icon and its aria-label clearly communicate the
* intent.
*/
class IconButtonComponent extends ButtonBaseDirective {
constructor(_idService, _dropdownEventService) {
super(_idService);
this._dropdownEventService = _dropdownEventService;
/**
* Binding host CSS class to component wrapper
*/
this._classes = 'fudis-icon-button-host';
/**
* Icon for button
*/
this.icon = undefined;
/**
* Assign button as menu button with dropdown
*/
this.asMenuButton = false;
/**
* Left icon margin for small and medium sizes
*/
this._iconMargin = new BehaviorSubject('fudis-button__icon');
/**
* Toggle dropdown menu button
*/
this.dropdownOpen = new BehaviorSubject(false);
}
ngOnInit() {
const margin = ['medium', 'small'].includes(this.size) ? ' fudis-ml-xs' : '';
this._iconMargin.next(`fudis-button__icon${margin}`);
super.ngOnInit();
}
ngOnChanges(changes) {
const variant = changes.variant?.currentValue !== changes.variant?.previousValue;
const disabled = changes.disabled?.currentValue !== changes.disabled?.previousValue;
const size = changes.size?.currentValue !== changes.size?.previousValue;
if (variant || disabled) {
this._classList.next(this._getClasses());
}
if (size) {
this._size = this.size;
const margin = ['medium', 'small'].includes(this.size) ? ' fudis-ml-xs' : '';
this._iconMargin.next(`fudis-button__icon${margin}`);
this._classList.next(this._getClasses());
}
}
/**
* Icon Button click event
*/
buttonClick(event) {
if (this.asMenuButton) {
this.toggleMenu();
}
this.handleClick.emit(event);
}
/**
* Toggling when Icon Button is used as menu button
*/
toggleMenu() {
const newState = !this.dropdownOpen.value;
this.dropdownOpen.next(newState);
this._dropdownEventService.triggerWidthCalculation();
}
/**
* Close dropdown when Icon Button is used as menu button
*/
closeMenu(focusToButton = true) {
this.dropdownOpen.next(false);
if (!this._focused && focusToButton) {
this.buttonEl.nativeElement.focus();
}
this._dropdownEventService.triggerWidthCalculation();
}
/**
* Handler for blurring out and closing menu button's dropdown
*/
_handleButtonBlur(event) {
this._focused = false;
const targetIsDropdownMenuOption = event.relatedTarget?.classList?.contains('fudis-dropdown-menu-item');
if (this.asMenuButton && !targetIsDropdownMenuOption) {
this.dropdownOpen.next(false);
}
this.handleBlur.emit(event);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: IconButtonComponent, deps: [{ token: FudisIdService }, { token: DropdownEventService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: IconButtonComponent, isStandalone: true, selector: "fudis-icon-button", inputs: { ariaLabel: "ariaLabel", icon: "icon", asMenuButton: "asMenuButton" }, host: { properties: { "class": "this._classes" } }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "@if (popoverText) {\n <button\n fudisPopover\n #buttonElement\n [class]=\"_classList | async\"\n type=\"button\"\n (click)=\"!disabled && buttonClick($event)\"\n (blur)=\"!disabled && _handleButtonBlur($event)\"\n (focus)=\"!disabled && _handleButtonFocus($event)\"\n (keydown)=\"(!disabled)\"\n [attr.tabIndex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"disabled || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.id]=\"id\"\n [popoverPosition]=\"popoverPosition\"\n [popoverText]=\"popoverText\"\n >\n @if (icon) {\n <div [class]=\"_iconMargin | async\">\n @if (_iconColor | async; as iconColor) {\n <fudis-icon\n class=\"fudis-button__icon__component\"\n [color]=\"iconColor\"\n [icon]=\"icon\"\n [rotate]=\"iconRotate\"\n ></fudis-icon>\n }\n </div>\n }\n </button>\n}\n\n@if (!popoverText) {\n <button\n #buttonElement\n [class]=\"_classList | async\"\n type=\"button\"\n (click)=\"!disabled && buttonClick($event)\"\n (blur)=\"!disabled && _handleButtonBlur($event)\"\n (focus)=\"!disabled && _handleButtonFocus($event)\"\n (keydown)=\"(!disabled)\"\n [attr.tabIndex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"disabled || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.id]=\"id\"\n [attr.aria-expanded]=\"!asMenuButton ? null : (dropdownOpen | async)\"\n [attr.aria-controls]=\"\n asMenuButton && dropdownMenuId && (dropdownOpen | async) ? dropdownMenuId : null\n \"\n [attr.aria-haspopup]=\"!asMenuButton ? null : 'menu'\"\n >\n @if (icon) {\n <div [class]=\"_iconMargin | async\">\n @if (_iconColor | async; as iconColor) {\n <fudis-icon\n class=\"fudis-button__icon__component\"\n [color]=\"iconColor\"\n [icon]=\"icon\"\n [rotate]=\"iconRotate\"\n ></fudis-icon>\n }\n </div>\n }\n </button>\n}\n\n@if (asMenuButton && (dropdownOpen | async)) {\n <ng-content></ng-content>\n}\n", styles: [".fudis-icon-button-host{display:inline-block;position:relative}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }, { kind: "directive", type: PopoverDirective, selector: "[fudisPopover]" }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: IconButtonComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-icon-button', encapsulation: ViewEncapsulation.None, imports: [CommonModule, IconComponent, PopoverDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (popoverText) {\n <button\n fudisPopover\n #buttonElement\n [class]=\"_classList | async\"\n type=\"button\"\n (click)=\"!disabled && buttonClick($event)\"\n (blur)=\"!disabled && _handleButtonBlur($event)\"\n (focus)=\"!disabled && _handleButtonFocus($event)\"\n (keydown)=\"(!disabled)\"\n [attr.tabIndex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"disabled || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.id]=\"id\"\n [popoverPosition]=\"popoverPosition\"\n [popoverText]=\"popoverText\"\n >\n @if (icon) {\n <div [class]=\"_iconMargin | async\">\n @if (_iconColor | async; as iconColor) {\n <fudis-icon\n class=\"fudis-button__icon__component\"\n [color]=\"iconColor\"\n [icon]=\"icon\"\n [rotate]=\"iconRotate\"\n ></fudis-icon>\n }\n </div>\n }\n </button>\n}\n\n@if (!popoverText) {\n <button\n #buttonElement\n [class]=\"_classList | async\"\n type=\"button\"\n (click)=\"!disabled && buttonClick($event)\"\n (blur)=\"!disabled && _handleButtonBlur($event)\"\n (focus)=\"!disabled && _handleButtonFocus($event)\"\n (keydown)=\"(!disabled)\"\n [attr.tabIndex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"disabled || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.id]=\"id\"\n [attr.aria-expanded]=\"!asMenuButton ? null : (dropdownOpen | async)\"\n [attr.aria-controls]=\"\n asMenuButton && dropdownMenuId && (dropdownOpen | async) ? dropdownMenuId : null\n \"\n [attr.aria-haspopup]=\"!asMenuButton ? null : 'menu'\"\n >\n @if (icon) {\n <div [class]=\"_iconMargin | async\">\n @if (_iconColor | async; as iconColor) {\n <fudis-icon\n class=\"fudis-button__icon__component\"\n [color]=\"iconColor\"\n [icon]=\"icon\"\n [rotate]=\"iconRotate\"\n ></fudis-icon>\n }\n </div>\n }\n </button>\n}\n\n@if (asMenuButton && (dropdownOpen | async)) {\n <ng-content></ng-content>\n}\n", styles: [".fudis-icon-button-host{display:inline-block;position:relative}\n"] }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: DropdownEventService }], propDecorators: { _classes: [{
type: HostBinding,
args: ['class']
}], ariaLabel: [{
type: Input,
args: [{ required: true }]
}], icon: [{
type: Input,
args: [{ required: true }]
}], asMenuButton: [{
type: Input
}] } });
/**
* Groups related form controls under a common context.
*
* Use this component to improve form structure, clarity, and accessibility.
*/
class FieldSetComponent extends PopoverApiDirective {
constructor(_translationService, _element, _errorSummaryService, _focusService, _idService) {
super();
this._translationService = _translationService;
this._element = _element;
this._errorSummaryService = _errorSummaryService;
this._focusService = _focusService;
this._idService = _idService;
/**
* Maximum width of Grid. When viewport gets narrower, grid automatically adjusts to lower sizes.
*
* - Xxl = Default value. Viewports of 1600px and larger
* - Xl = Viewports smaller than 1600px
* - Lg = Viewports smaller than 1200px
* - Md = Viewports smaller than 992px
* - Sm = Viewports smaller than 768px
* - Xs = Viewports smaller than 576px
* - Full-width = Grid takes full width of its parent container, no maximum width
*/
this.width = 'xxl';
/**
* Alignment of Grid component inside its parent
*/
this.align = 'start';
/**
* Set focus to Fieldset when it appears first time
*/
this.initialFocus = false;
/**
* Send information about current Fieldset to Error Summary Service. Error Summary Breadcrumb is
* the label of the current Fieldset and is visible in the clickable link in Error Summary.
*/
this.errorSummaryBreadcrumb = true;
/**
* Visual size of label legend. Default 'md' and 'sm' is similar to standard input label, used in
* e. g. RadioButtonGroup.
*/
this.labelSize = 'md';
/**
* Used to set visible focus on if focus event comes from Error Summary link.
*/
this._legendFocusVisible = false;
/**
* Has Fieldset been added to Error Summary
*/
this._fieldsetSent = false;
}
ngOnInit() {
this._setFieldsetId();
this._addToErrorSummary(this.label);
this._setClasses();
}
ngAfterContentInit() {
this._errorSummaryService
.getFormAncestorId(this._element.nativeElement)
.then((parentFormId) => {
if (parentFormId) {
this._parentFormId = parentFormId;
this._addToErrorSummary(this.label);
}
});
this._fieldsetGuidance = this._element.nativeElement.querySelector('fudis-guidance');
}
ngAfterViewInit() {
if (this.initialFocus && !this._focusService.isIgnored(this.id)) {
this._fieldsetLegend?.nativeElement?.focus();
}
const className = this._element.nativeElement.className;
const selectionGroups = ['fudis-radio-button-group', 'fudis-checkbox-group'];
if (selectionGroups.includes(className))
this._hasSelectionGroupParent();
}
ngOnChanges(changes) {
if (changes.label?.currentValue &&
changes.label?.currentValue !== changes.label?.previousValue) {
this._addToErrorSummary(changes.label?.currentValue);
}
if (changes.inputSize?.currentValue !== changes.inputSize?.previousValue) {
this._setClasses();
}
}
ngOnDestroy() {
this._removeFromErrorSummary();
}
_handleLegendBlur() {
this._legendFocusVisible = false;
}
_handleFocus(event) {
if (event.relatedTarget) {
const elementHasLinkClass = event.relatedTarget.classList.contains('fudis-link');
if (elementHasLinkClass) {
this._legendFocusVisible = true;
this._fieldsetLegend.nativeElement.focus();
}
}
}
/**
* Determines if Fieldset has CheckboxGroup or RadioButtonGroup as a parent. If so, adds Guidance
* helptext as part of the Fieldset legend element and makes it accessible for screen readers.
*/
_hasSelectionGroupParent() {
const guidanceHelpText = this._fieldsetGuidance
?.querySelector('.fudis-guidance__help-text')
?.textContent?.trim();
if (!guidanceHelpText)
return;
const groupHelpText = this._element.nativeElement.querySelector('.fudis-fieldset__legend__main__group-helptext');
if (!groupHelpText)
return;
groupHelpText.removeAttribute('aria-hidden');
const helptext = document.createElement('span');
helptext.textContent = guidanceHelpText;
groupHelpText.appendChild(helptext);
}
/**
* Add Fieldset label to Error Summary
*/
_addToErrorSummary(label) {
if (this.errorSummaryBreadcrumb && this._parentFormId) {
const fieldsetInfo = {
id: this.id,
formId: this._parentFormId,
title: label,
};
this._errorSummaryService.addFieldset(fieldsetInfo);
this._fieldsetSent = true;
}
}
/**
* Remove Fieldset label from Error Summary
*/
_removeFromErrorSummary() {
if (this._fieldsetSent && this._parentFormId) {
this._errorSummaryService.removeFieldset(this._parentFormId, this.id);
}
}
/**
* Set CSS classes for native fieldset HTMLelement
*/
_setClasses() {
if (this.inputSize) {
this._classes = ['fudis-fieldset', `fudis-input-size__${this.inputSize}`];
}
else {
this._classes = ['fudis-fieldset'];
}
}
/**
* Add or generate id with IdService
*/
_setFieldsetId() {
if (this.id) {
this._idService.addNewId('fieldset', this.id);
}
else {
this.id = this._idService.getNewId('fieldset');
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FieldSetComponent, deps: [{ token: FudisTranslationService }, { token: i0.ElementRef }, { token: FudisInternalErrorSummaryService }, { token: FudisFocusService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: FieldSetComponent, isStandalone: true, selector: "fudis-fieldset", inputs: { label: "label", width: "width", inputSize: "inputSize", align: "align", initialFocus: "initialFocus", errorSummaryBreadcrumb: "errorSummaryBreadcrumb", required: "required", labelSize: "labelSize", id: "id", helpText: "helpText" }, viewQueries: [{ propertyName: "_fieldsetLegend", first: true, predicate: ["fieldsetLegend"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<!-- prettier-ignore -->\n<div\n fudisGrid\n [align]=\"align\"\n [width]=\"inputSize ? 'initial' : width\"\n [serviceDefaults]=\"false\"\n [rowGap]=\"'none'\"\n>\n <fieldset [attr.id]=\"id\" [tabIndex]=\"-1\" [class]=\"_classes\">\n <legend class=\"fudis-fieldset__legend\">\n <div\n #fieldsetLegend\n class=\"fudis-fieldset__legend__main fudis-fieldset__legend__{{ labelSize }}\"\n [class.fudis-fieldset__legend--focused]=\"_legendFocusVisible\"\n [attr.id]=\"id + '-legend'\"\n [tabIndex]=\"-1\"\n (focus)=\"_handleFocus($event)\"\n (blur)=\"_handleLegendBlur()\"\n >\n <div class=\"fudis-fieldset__legend__main__text-content\">\n <span class=\"fudis-fieldset__legend__main__text\"\n >{{ label }}@if (required) {\n <span class=\"fudis-fieldset__legend__main__space\"></span>\n }@if (required) {\n <span class=\"fudis-fieldset__legend__main__required\"\n >({{ _translationService.getTranslations()().REQUIRED }})</span\n >\n }\n </span>\n <div\n aria-hidden=\"true\"\n class=\"fudis-fieldset__legend__main__group-helptext fudis-visually-hidden\"\n ></div>\n </div>\n @if (popoverText && popoverTriggerLabel) {\n <fudis-icon-button\n class=\"fudis-fieldset__legend__main__popover-button\"\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'info-circle'\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [ariaLabel]=\"popoverTriggerLabel\"\n />\n }\n </div>\n </legend>\n @if (helpText) {\n <fudis-body-text class=\"fudis-fieldset__help-text\" [variant]=\"'sm-regular'\">{{\n helpText\n }}</fudis-body-text>\n }\n <ng-content select=\"fudis-fieldset-actions\"></ng-content>\n <ng-content select=\"fudis-fieldset-content\"></ng-content>\n </fieldset>\n</div>\n", styles: ["@media screen and (min-width:36em){.fudis-fieldset__legend:has(~.fudis-fieldset-actions__align--start) .fudis-fieldset__legend__main,.fudis-fieldset__legend:has(~.fudis-fieldset-actions__align--end) .fudis-fieldset__legend__main{margin-right:calc(calc(2.5rem / var(--fudis-rem-multiplier)) / var(--fudis-rem-multiplier))}}.fudis-fieldset .fudis-notification{margin-bottom:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-fieldset-actions{display:flex;flex-wrap:wrap;gap:calc(.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));align-items:center;align-self:center;justify-content:flex-start}@media screen and (min-width:36em){.fudis-fieldset-actions__align--end{justify-content:flex-end;margin-top:0}}.fudis-fieldset-actions__align--start,.fudis-fieldset-actions__align--end{margin-top:calc(.5rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-fieldset-actions__align--start,.fudis-fieldset-actions__align--end{grid-row:1/3;grid-column:2/2;margin-top:0}}@media screen and (min-width:36em){.fudis-fieldset:has(.fudis-fieldset-actions__align--start),.fudis-fieldset:has(.fudis-fieldset-actions__align--end){grid-template-columns:auto 1fr}}\n"], dependencies: [{ kind: "directive", type: GridDirective, selector: "[fudisGrid]" }, { kind: "component", type: IconButtonComponent, selector: "fudis-icon-button", inputs: ["ariaLabel", "icon", "asMenuButton"] }, { kind: "component", type: BodyTextComponent, selector: "fudis-body-text", inputs: ["variant", "align", "lang"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FieldSetComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-fieldset', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [GridDirective, IconButtonComponent, BodyTextComponent], template: "<!-- prettier-ignore -->\n<div\n fudisGrid\n [align]=\"align\"\n [width]=\"inputSize ? 'initial' : width\"\n [serviceDefaults]=\"false\"\n [rowGap]=\"'none'\"\n>\n <fieldset [attr.id]=\"id\" [tabIndex]=\"-1\" [class]=\"_classes\">\n <legend class=\"fudis-fieldset__legend\">\n <div\n #fieldsetLegend\n class=\"fudis-fieldset__legend__main fudis-fieldset__legend__{{ labelSize }}\"\n [class.fudis-fieldset__legend--focused]=\"_legendFocusVisible\"\n [attr.id]=\"id + '-legend'\"\n [tabIndex]=\"-1\"\n (focus)=\"_handleFocus($event)\"\n (blur)=\"_handleLegendBlur()\"\n >\n <div class=\"fudis-fieldset__legend__main__text-content\">\n <span class=\"fudis-fieldset__legend__main__text\"\n >{{ label }}@if (required) {\n <span class=\"fudis-fieldset__legend__main__space\"></span>\n }@if (required) {\n <span class=\"fudis-fieldset__legend__main__required\"\n >({{ _translationService.getTranslations()().REQUIRED }})</span\n >\n }\n </span>\n <div\n aria-hidden=\"true\"\n class=\"fudis-fieldset__legend__main__group-helptext fudis-visually-hidden\"\n ></div>\n </div>\n @if (popoverText && popoverTriggerLabel) {\n <fudis-icon-button\n class=\"fudis-fieldset__legend__main__popover-button\"\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'info-circle'\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [ariaLabel]=\"popoverTriggerLabel\"\n />\n }\n </div>\n </legend>\n @if (helpText) {\n <fudis-body-text class=\"fudis-fieldset__help-text\" [variant]=\"'sm-regular'\">{{\n helpText\n }}</fudis-body-text>\n }\n <ng-content select=\"fudis-fieldset-actions\"></ng-content>\n <ng-content select=\"fudis-fieldset-content\"></ng-content>\n </fieldset>\n</div>\n", styles: ["@media screen and (min-width:36em){.fudis-fieldset__legend:has(~.fudis-fieldset-actions__align--start) .fudis-fieldset__legend__main,.fudis-fieldset__legend:has(~.fudis-fieldset-actions__align--end) .fudis-fieldset__legend__main{margin-right:calc(calc(2.5rem / var(--fudis-rem-multiplier)) / var(--fudis-rem-multiplier))}}.fudis-fieldset .fudis-notification{margin-bottom:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-fieldset-actions{display:flex;flex-wrap:wrap;gap:calc(.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));align-items:center;align-self:center;justify-content:flex-start}@media screen and (min-width:36em){.fudis-fieldset-actions__align--end{justify-content:flex-end;margin-top:0}}.fudis-fieldset-actions__align--start,.fudis-fieldset-actions__align--end{margin-top:calc(.5rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-fieldset-actions__align--start,.fudis-fieldset-actions__align--end{grid-row:1/3;grid-column:2/2;margin-top:0}}@media screen and (min-width:36em){.fudis-fieldset:has(.fudis-fieldset-actions__align--start),.fudis-fieldset:has(.fudis-fieldset-actions__align--end){grid-template-columns:auto 1fr}}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: i0.ElementRef }, { type: FudisInternalErrorSummaryService }, { type: FudisFocusService }, { type: FudisIdService }], propDecorators: { _fieldsetLegend: [{
type: ViewChild,
args: ['fieldsetLegend']
}], label: [{
type: Input,
args: [{ required: true }]
}], width: [{
type: Input
}], inputSize: [{
type: Input
}], align: [{
type: Input
}], initialFocus: [{
type: Input
}], errorSummaryBreadcrumb: [{
type: Input
}], required: [{
type: Input
}], labelSize: [{
type: Input
}], id: [{
type: Input
}], helpText: [{
type: Input
}] } });
/**
* Identifies the actions associated with a fieldset.
*
* Use this directive to add buttons that affect the fieldset as a whole.
*/
class FieldsetActionsDirective {
constructor() {
this.hostClass = 'fudis-fieldset-actions fudis-fieldset-actions__align--start';
/**
* Alignment of Fieldset Actions container
*/
this.align = 'start';
}
ngOnChanges(changes) {
if (changes.align?.currentValue !== changes.align?.previousValue) {
this.hostClass = `fudis-fieldset-actions fudis-fieldset-actions__align--${changes.align?.currentValue}`;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FieldsetActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: FieldsetActionsDirective, isStandalone: true, selector: "fudis-fieldset-actions", inputs: { align: "align" }, host: { properties: { "class": "this.hostClass" } }, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FieldsetActionsDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-fieldset-actions' }]
}], propDecorators: { hostClass: [{
type: HostBinding,
args: ['class']
}], align: [{
type: Input
}] } });
/**
* Identifies the main content area of a fieldset.
*
* Use this directive to group related form controls within a fieldset.
*/
class FieldsetContentDirective {
constructor() {
this._hostClass = 'fudis-fieldset-content';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FieldsetContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: FieldsetContentDirective, isStandalone: true, selector: "fudis-fieldset-content", host: { properties: { "class": "this._hostClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FieldsetContentDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-fieldset-content' }]
}], propDecorators: { _hostClass: [{
type: HostBinding,
args: ['class']
}] } });
/**
* Groups multiple related checkbox options.
*
* Use this component when user can select value(s) from a set. Each `fudis-checkbox-group-option`
* child must have a `[controlName]` matching a key in the FormGroup. Use `FudisGroupValidators` for
* group-level validation (e.g., `oneRequired`, `min`, `max`).
*
* @example
* ```html
* <fudis-checkbox-group [label]="'Colors'" [formGroup]="colorsGroup">
* <fudis-checkbox-group-option [controlName]="'red'" [label]="'Red'"></fudis-checkbox-group-option>
* <fudis-checkbox-group-option [controlName]="'blue'" [label]="'Blue'"></fudis-checkbox-group-option>
* </fudis-checkbox-group>
* ```;
*/
class CheckboxGroupComponent extends GroupComponentBaseDirective {
constructor(_document, _idService, _focusService) {
super(_idService, _focusService);
this._document = _document;
/**
* Width of Checkbox Group: 'sm' | 'md' | 'lg' | 'full-width'
*/
this.size = 'lg';
/**
* Emit changed control's name and whole FormGroup when one Checkbox is clicked.
*/
this.handleChange = new EventEmitter();
/**
* To determine if focus has been moved out from the whole checkbox group, so possible errors will
* not show before that.
*/
this._groupBlurredOut = signal(false, ...(ngDevMode ? [{ debugName: "_groupBlurredOut" }] : /* istanbul ignore next */ []));
this.groupBlurredOut = this._groupBlurredOut.asReadonly();
/**
* Private signals for managing the touched, invalid, and disabled state of the form group.
*/
this._touchedState = signal(false, ...(ngDevMode ? [{ debugName: "_touchedState" }] : /* istanbul ignore next */ []));
this._invalidState = signal(false, ...(ngDevMode ? [{ debugName: "_invalidState" }] : /* istanbul ignore next */ []));
this._disabledState = signal(false, ...(ngDevMode ? [{ debugName: "_disabledState" }] : /* istanbul ignore next */ []));
/**
* Publicly exposed readonly signals reflecting the current state of the form group. Used by child
* checkbox options to track group state reactively without requiring markForCheck().
*/
this.touchedState = this._touchedState.asReadonly();
this.invalidState = this._invalidState.asReadonly();
this.disabledState = this._disabledState.asReadonly();
/**
* Computed signal that combines touched, invalid, and blurred-out state to determine if group
* errors should be shown.
*/
this.groupErrors = computed(() => this._touchedState() && this._invalidState() && this._groupBlurredOut(), ...(ngDevMode ? [{ debugName: "groupErrors" }] : /* istanbul ignore next */ []));
this._updateValueAndValidityTrigger.pipe(takeUntilDestroyed()).subscribe(() => {
if (this.formGroup) {
this._required.next(FudisValidatorUtilities.oneRequiredOrMin(this.formGroup));
}
});
}
/**
* Copy FormGroup states into local signals so child checkbox options can track group state
* reactively without requiring markForCheck().
*/
_syncFormGroupState() {
this._touchedState.set(this.formGroup.touched);
this._invalidState.set(this.formGroup.invalid);
this._disabledState.set(this.formGroup.disabled);
}
_applyGroupMarkAsTouched() {
if (this.formGroup.touched) {
this._groupBlurredOut.set(true);
}
else {
/**
* Extend original markAllAsTouched function to change _groupBlurredOut value to 'true', so
* error messages are loaded when e.g. on Submit touched value is changed programatically.
*/
const originalMarkAllAsTouched = this.formGroup.markAllAsTouched;
this.formGroup.markAllAsTouched = () => {
originalMarkAllAsTouched.apply(this.formGroup);
this._syncFormGroupState();
this._groupBlurredOut.set(true);
};
}
}
ngOnInit() {
this._setParentComponentId('checkbox-group');
this._syncFormGroupState();
this._required.next(FudisValidatorUtilities.oneRequiredOrMin(this.formGroup));
this.formGroup.events.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => {
this._syncFormGroupState();
this._updateValueAndValidityTrigger.next();
});
this._applyGroupMarkAsTouched();
}
ngAfterViewInit() {
super.ngAfterViewInit();
/**
* For Screen reader users bind guidance text to first checkbox child input
*/
const checkboxIds = this._idService.getAllChildrenIds('checkbox-group', this.id);
const firstCheckbox = this._getFirstEnabledCheckbox(checkboxIds);
const guidanceId = this._guidance?.['_id'];
if (firstCheckbox && guidanceId) {
firstCheckbox.setAttribute('aria-describedby', guidanceId);
}
}
/**
* Return first checkbox child input when it is not disabled
*/
_getFirstEnabledCheckbox(ids) {
for (const id of ids) {
const checkbox = this._document.getElementById(id);
if (checkbox instanceof HTMLInputElement && !checkbox.disabled) {
return checkbox;
}
}
return null;
}
/**
* Used to display possible error messages only when focus has moved out from all the group's
* checkboxes.
*/
setGroupBlurredOut(value) {
this._groupBlurredOut.set(value);
}
/**
* When child Checkbox component is clicked, it calls this parent's function, which will then
* trigger Output emit.
*
* @param changedControlName Name of the clicked control
*/
triggerEmit(changedControlName) {
this.handleChange.emit({
changedControlName: changedControlName,
formGroup: this.formGroup,
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CheckboxGroupComponent, deps: [{ token: DOCUMENT }, { token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: CheckboxGroupComponent, isStandalone: true, selector: "fudis-checkbox-group", inputs: { formGroup: "formGroup", size: "size" }, outputs: { handleChange: "handleChange" }, viewQueries: [{ propertyName: "_guidance", first: true, predicate: ["checkboxGroupGuidance"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<fudis-fieldset\n class=\"fudis-checkbox-group\"\n [id]=\"id\"\n [labelSize]=\"'sm'\"\n [label]=\"label\"\n [required]=\"_required | async\"\n [errorSummaryBreadcrumb]=\"false\"\n [initialFocus]=\"initialFocus\"\n [inputSize]=\"size\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n>\n <fudis-fieldset-content>\n <ng-content select=\"fudis-checkbox-group-option\" />\n @if (formGroup && !disableGuidance) {\n <fudis-guidance\n #checkboxGroupGuidance\n [inputLabel]=\"label\"\n [helpText]=\"helpText\"\n [for]=\"id + '-legend'\"\n [groupBlurredOut]=\"groupBlurredOut()\"\n [formGroup]=\"formGroup\"\n [groupHelpTextHidden]=\"true\"\n >\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n </fudis-fieldset-content>\n</fudis-fieldset>\n", styles: [".fudis-checkbox-group{box-sizing:border-box;margin:0;padding:0}\n"], dependencies: [{ kind: "component", type: FieldSetComponent, selector: "fudis-fieldset", inputs: ["label", "width", "inputSize", "align", "initialFocus", "errorSummaryBreadcrumb", "required", "labelSize", "id", "helpText"] }, { kind: "directive", type: FieldsetContentDirective, selector: "fudis-fieldset-content" }, { kind: "component", type: GuidanceComponent, selector: "fudis-guidance", inputs: ["for", "inputLabel", "control", "formGroup", "helpText", "maxLength", "selectedOption", "groupBlurredOut", "groupHelpTextHidden"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CheckboxGroupComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-checkbox-group', imports: [
FieldSetComponent,
FieldsetContentDirective,
GuidanceComponent,
FormsModule,
ReactiveFormsModule,
AsyncPipe,
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fudis-fieldset\n class=\"fudis-checkbox-group\"\n [id]=\"id\"\n [labelSize]=\"'sm'\"\n [label]=\"label\"\n [required]=\"_required | async\"\n [errorSummaryBreadcrumb]=\"false\"\n [initialFocus]=\"initialFocus\"\n [inputSize]=\"size\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n>\n <fudis-fieldset-content>\n <ng-content select=\"fudis-checkbox-group-option\" />\n @if (formGroup && !disableGuidance) {\n <fudis-guidance\n #checkboxGroupGuidance\n [inputLabel]=\"label\"\n [helpText]=\"helpText\"\n [for]=\"id + '-legend'\"\n [groupBlurredOut]=\"groupBlurredOut()\"\n [formGroup]=\"formGroup\"\n [groupHelpTextHidden]=\"true\"\n >\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n </fudis-fieldset-content>\n</fudis-fieldset>\n", styles: [".fudis-checkbox-group{box-sizing:border-box;margin:0;padding:0}\n"] }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: FudisIdService }, { type: FudisFocusService }], propDecorators: { _guidance: [{
type: ViewChild,
args: ['checkboxGroupGuidance']
}], formGroup: [{
type: Input,
args: [{ required: true }]
}], size: [{
type: Input
}], handleChange: [{
type: Output
}] } });
/**
* Single checkbox option for CheckboxGroupComponent.
*/
class CheckboxGroupOptionComponent {
constructor(_idService, _checkboxGroup) {
this._idService = _idService;
this._checkboxGroup = _checkboxGroup;
/**
* Emits changed Checkbox and its control.
*/
this.handleChange = new EventEmitter();
/**
* If checkbox has focus
*/
this._focused = false;
}
ngOnInit() {
if (this.id) {
this._idService.addNewChildId('checkbox-group', this._checkboxGroup.id, this.id);
}
else {
this.id = this._idService.getNewChildId('checkbox-group', this._checkboxGroup.id);
}
const parentControl = this._checkboxGroup?.formGroup?.controls?.[this.controlName];
if (this.controlName && parentControl) {
/**
* Set Checkbox's control to match one in parent FormGroup.
*/
this._control = parentControl;
}
}
/**
* For toggling checkbox
*/
_onChange() {
const optionToEmit = {
id: this.id,
groupName: this._checkboxGroup.id,
controlName: this.controlName,
label: this.label,
value: this._checkboxGroup.formGroup.controls[this.controlName]?.['value'],
};
/**
* This Checkbox's emit
*/
this.handleChange.emit({ checkbox: optionToEmit, control: this._control });
/**
* Call parent's function, which triggers Checkbox Group's emit
*/
this._checkboxGroup.triggerEmit(this.controlName);
}
/**
* When focusing out from checkbox, determine if next focus target is outside of the same checkbox
* group. If yes, then tell parent Checkbox Group, that focus has moved outside. --> If there are
* validation errors, those should become visible.
*/
_onBlur(event) {
this._focused = false;
if (event.relatedTarget?.getAttribute('name') !== this._checkboxGroup.id) {
setTimeout(() => {
if (document.activeElement?.getAttribute('name') !== this._checkboxGroup.id) {
this._checkboxGroup.handleBlur.emit(event);
this._checkboxGroup.setGroupBlurredOut(true);
}
}, 150);
}
}
/**
* When Checkbox is focused
*/
_onFocus(event) {
this._focused = true;
this._checkboxGroup.onFocus(event);
}
/**
* If control is disabled, prevent toggling it.
*/
_checkboxClick(event) {
if (this._control.disabled) {
event.preventDefault();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CheckboxGroupOptionComponent, deps: [{ token: FudisIdService }, { token: CheckboxGroupComponent, host: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: CheckboxGroupOptionComponent, isStandalone: true, selector: "fudis-checkbox-group-option", inputs: { controlName: "controlName", label: "label", id: "id" }, outputs: { handleChange: "handleChange" }, ngImport: i0, template: "@let groupErrors = _checkboxGroup.groupErrors();\n\n@if (_control) {\n <label\n class=\"fudis-checkbox fudis-checkbox__label\"\n [attr.for]=\"id\"\n [id]=\"id + '-label'\"\n [class.fudis-checkbox--disabled]=\"_control.disabled || _checkboxGroup.disabled\"\n >\n <input\n (click)=\"_checkboxClick($event)\"\n type=\"checkbox\"\n class=\"fudis-checkbox__input\"\n [id]=\"id\"\n [formControl]=\"_control\"\n [value]=\"_control.value\"\n [name]=\"_checkboxGroup.id\"\n [attr.tabindex]=\"_control.disabled || _checkboxGroup.disabled ? -1 : null\"\n [attr.aria-disabled]=\"_control.disabled || _checkboxGroup.disabled || null\"\n [attr.aria-invalid]=\"groupErrors || null\"\n (change)=\"_onChange()\"\n (blur)=\"_onBlur($event)\"\n (focus)=\"_onFocus($event)\"\n (keyup)=\"_checkboxGroup.handleKeyUp.emit($event)\"\n />\n <div\n class=\"fudis-checkbox__content\"\n [class.fudis-checkbox__content--disabled]=\"_control.disabled || _checkboxGroup.disabled\"\n >\n <div class=\"fudis-checkbox__content-wrapper\">\n <span\n class=\"fudis-checkbox__content__box\"\n [class.fudis-checkbox__content__box--focused]=\"_focused\"\n [class.fudis-checkbox__content__box--invalid]=\"groupErrors\"\n [class.fudis-checkbox__content__box--disabled]=\"\n _control.disabled || _checkboxGroup.disabled\n \"\n >\n @if (_control.value) {\n <fudis-icon [icon]=\"'check'\" class=\"fudis-checkbox__content__box__icon\" />\n }\n </span>\n </div>\n <span class=\"fudis-checkbox__content__label\">\n {{ label }}\n </span>\n </div>\n </label>\n}\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CheckboxGroupOptionComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-checkbox-group-option', encapsulation: ViewEncapsulation.None, imports: [FormsModule, ReactiveFormsModule, IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let groupErrors = _checkboxGroup.groupErrors();\n\n@if (_control) {\n <label\n class=\"fudis-checkbox fudis-checkbox__label\"\n [attr.for]=\"id\"\n [id]=\"id + '-label'\"\n [class.fudis-checkbox--disabled]=\"_control.disabled || _checkboxGroup.disabled\"\n >\n <input\n (click)=\"_checkboxClick($event)\"\n type=\"checkbox\"\n class=\"fudis-checkbox__input\"\n [id]=\"id\"\n [formControl]=\"_control\"\n [value]=\"_control.value\"\n [name]=\"_checkboxGroup.id\"\n [attr.tabindex]=\"_control.disabled || _checkboxGroup.disabled ? -1 : null\"\n [attr.aria-disabled]=\"_control.disabled || _checkboxGroup.disabled || null\"\n [attr.aria-invalid]=\"groupErrors || null\"\n (change)=\"_onChange()\"\n (blur)=\"_onBlur($event)\"\n (focus)=\"_onFocus($event)\"\n (keyup)=\"_checkboxGroup.handleKeyUp.emit($event)\"\n />\n <div\n class=\"fudis-checkbox__content\"\n [class.fudis-checkbox__content--disabled]=\"_control.disabled || _checkboxGroup.disabled\"\n >\n <div class=\"fudis-checkbox__content-wrapper\">\n <span\n class=\"fudis-checkbox__content__box\"\n [class.fudis-checkbox__content__box--focused]=\"_focused\"\n [class.fudis-checkbox__content__box--invalid]=\"groupErrors\"\n [class.fudis-checkbox__content__box--disabled]=\"\n _control.disabled || _checkboxGroup.disabled\n \"\n >\n @if (_control.value) {\n <fudis-icon [icon]=\"'check'\" class=\"fudis-checkbox__content__box__icon\" />\n }\n </span>\n </div>\n <span class=\"fudis-checkbox__content__label\">\n {{ label }}\n </span>\n </div>\n </label>\n}\n" }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: CheckboxGroupComponent, decorators: [{
type: Host
}] }], propDecorators: { controlName: [{
type: Input,
args: [{ required: true }]
}], label: [{
type: Input,
args: [{ required: true }]
}], id: [{
type: Input
}], handleChange: [{
type: Output
}] } });
const fudisInputSizeArray = ['sm', 'md', 'lg', 'full-width'];
const FudisDateInputFormat = {
dateInput: 'DD.MM.YYYY',
monthYearLabel: 'MMM YYYY',
};
const FUDIS_DATE_FORMATS = {
...MAT_NATIVE_DATE_FORMATS,
parse: {
dateInput: 'DD.MM.YYYY',
},
display: {
...MAT_NATIVE_DATE_FORMATS.display,
dateInput: FudisDateInputFormat,
},
};
/**
* Update locale for Fudis translations in Datepicker
*
* @param value Language Signal string value
* @returns Locale code
*/
const updateLocale = (value) => {
switch (value) {
case 'en':
return 'en-GB';
case 'fi':
return 'fi-FI';
case 'sv':
return 'sv-SE';
default:
return 'en-GB';
}
};
/**
* Assign Fudis translation keys for Datepicker
*/
const updateMatDatePickerTranslations = (translations, datepickerIntl) => {
const matDatePickerIntlToReturn = datepickerIntl;
matDatePickerIntlToReturn.calendarLabel = translations.DATEPICKER.CALENDAR;
matDatePickerIntlToReturn.closeCalendarLabel = translations.DATEPICKER.CLOSE;
matDatePickerIntlToReturn.openCalendarLabel = translations.DATEPICKER.OPEN;
matDatePickerIntlToReturn.prevMonthLabel = translations.DATEPICKER.PREV_MONTH;
matDatePickerIntlToReturn.nextMonthLabel = translations.DATEPICKER.NEXT_MONTH;
matDatePickerIntlToReturn.prevYearLabel = translations.DATEPICKER.PREV_YEAR;
matDatePickerIntlToReturn.nextYearLabel = translations.DATEPICKER.NEXT_YEAR;
matDatePickerIntlToReturn.prevMultiYearLabel = translations.DATEPICKER.PREV_MULTIYEAR;
matDatePickerIntlToReturn.nextMultiYearLabel = translations.DATEPICKER.NEXT_MULTIYEAR;
matDatePickerIntlToReturn.switchToMonthViewLabel = translations.DATEPICKER.SWITCH_MONTH_VIEW;
matDatePickerIntlToReturn.switchToMultiYearViewLabel =
translations.DATEPICKER.SWITCH_MULTIYEAR_VIEW;
return matDatePickerIntlToReturn;
};
/**
* Parse date input to number array
*
* @returns New Date(year, monthIndex, day) or null
*/
const parseDate = (value) => {
// Split input value by non number values, e.g. 25/5/1977 or 25.5.1977 --> [25,5,1977]
const numberArray = value
? value
.split(/[^\d+]+/)
.filter(Number)
.map(Number)
: null;
// If day, month and year are provided. Year must have 4 digits.
if (numberArray?.length === 3 && numberArray[2].toString().length === 4) {
const newDate = new Date(numberArray[2], numberArray[1] - 1, numberArray[0]);
const dayUnchanged = newDate.getDate() === numberArray[0];
const monthUnchanged = newDate.getMonth() + 1 === numberArray[1];
const yearUnChanged = newDate.getFullYear() === numberArray[2];
if (dayUnchanged && monthUnchanged && yearUnChanged) {
return newDate;
}
else {
return null;
}
}
// If no year is provided, assume it is current year
if (numberArray?.length === 2) {
const currentYear = new Date().getFullYear();
const newDate = new Date(currentYear, numberArray[1] - 1, numberArray[0]);
const dayUnchanged = newDate.getDate() === numberArray[0];
const monthUnchanged = newDate.getMonth() + 1 === numberArray[1];
if (dayUnchanged && monthUnchanged) {
return newDate;
}
else {
return null;
}
}
// If input value is not parseable to Date object, return null
return null;
};
class FudisDateAdapter extends NativeDateAdapter {
/**
* Original file:
* https://github.com/angular/components/blob/main/src/material/core/datetime/native-date-adapter.ts
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parse(value) {
return parseDate(value);
}
format(date, displayFormat) {
if (!this.isValid(date)) {
throw Error('FudisDateAdapter: Cannot format invalid date.');
}
const dtf = new Intl.DateTimeFormat(this._selectLanguage(displayFormat), {
...displayFormat,
timeZone: 'utc',
});
return this._formatInputToDate(dtf, date);
}
/**
* Change the calendar starting day of the week from default 0 (Sunday) to 1 (Monday)
*/
getFirstDayOfWeek() {
return 1;
}
/**
* Determines from displayFormat value if the Date value is coming from the input field or from
* the datepicker calendar. This ensures, that visible input value is always in Finnish DD.MM.YYYY
* format, but calendar uses HTML lang in other context.
*/
_selectLanguage(displayFormat) {
if (Object.prototype.valueOf.call(displayFormat) === FudisDateInputFormat) {
return 'fi-FI';
}
return this.locale;
}
/**
* @param dtf Intl.DateTimeFormat object, containing the desired string format. It must have
* timeZone set to 'utc' to work fine
* @param date Date from which we want to get the string representation according to dtf
* @returns A Date object with its UTC representation based on the passed date info
*/
_formatInputToDate(dtf, date) {
// Passing the year to the constructor causes year numbers <100 to be converted to 19xx.
// To work around this we use `setUTCFullYear` and `setUTCHours` instead.
const d = new Date();
d.setUTCFullYear(date.getFullYear(), date.getMonth(), date.getDate());
d.setUTCHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
return dtf.format(d);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisDateAdapter, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisDateAdapter }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisDateAdapter, decorators: [{
type: Injectable
}] });
class ControlComponentBaseDirective extends FormCommonApiDirective {
constructor(_idService, _focusService) {
super(_idService, _focusService);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ControlComponentBaseDirective, deps: [{ token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: ControlComponentBaseDirective, isStandalone: true, selector: "[fudisControlComponentBase]", inputs: { control: "control" }, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ControlComponentBaseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisControlComponentBase]' }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: FudisFocusService }], propDecorators: { control: [{
type: Input,
args: [{ required: true }]
}] } });
class LabelComponent extends PopoverApiDirective {
constructor(_translationService) {
super();
this._translationService = _translationService;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LabelComponent, deps: [{ token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: LabelComponent, isStandalone: true, selector: "fudis-label", inputs: { id: "id", text: "text", for: "for", required: "required" }, usesInheritance: true, ngImport: i0, template: "<!-- prettier-ignore -->\n<label #labelElementRef class=\"fudis-label\" [attr.for]=\"for\" [id]=\"id\">\n <div class=\"fudis-label__content\"\n ><span class=\"fudis-label__content__text\"\n >{{ text }}@if (required) {\n <span class=\"fudis-label__content__space\"></span>\n }@if (required) {\n <span class=\"fudis-label__content__required\"\n >({{ _translationService.getTranslations()().REQUIRED }})</span\n >\n }\n </span>\n </div>\n @if (popoverText && popoverTriggerLabel) {\n <fudis-icon-button\n class=\"fudis-label__content__button\"\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'info-circle'\"\n [ariaLabel]=\"popoverTriggerLabel\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n />\n }\n</label>\n", dependencies: [{ kind: "component", type: IconButtonComponent, selector: "fudis-icon-button", inputs: ["ariaLabel", "icon", "asMenuButton"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LabelComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-label', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconButtonComponent], template: "<!-- prettier-ignore -->\n<label #labelElementRef class=\"fudis-label\" [attr.for]=\"for\" [id]=\"id\">\n <div class=\"fudis-label__content\"\n ><span class=\"fudis-label__content__text\"\n >{{ text }}@if (required) {\n <span class=\"fudis-label__content__space\"></span>\n }@if (required) {\n <span class=\"fudis-label__content__required\"\n >({{ _translationService.getTranslations()().REQUIRED }})</span\n >\n }\n </span>\n </div>\n @if (popoverText && popoverTriggerLabel) {\n <fudis-icon-button\n class=\"fudis-label__content__button\"\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'info-circle'\"\n [ariaLabel]=\"popoverTriggerLabel\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n />\n }\n</label>\n" }]
}], ctorParameters: () => [{ type: FudisTranslationService }], propDecorators: { id: [{
type: Input,
args: [{ required: true }]
}], text: [{
type: Input,
args: [{ required: true }]
}], for: [{
type: Input
}], required: [{
type: Input
}] } });
/**
* Allows selection of a start and end date.
*
* Use this component when user needs to define a time interval. Wrap two `fudis-datepicker`
* components — mark them with `fudisDateStart` and `fudisDateEnd` directives.
*
* @example
* ```html
* <fudis-date-range>
* <fudis-datepicker fudisDateStart [label]="'Start'" [control]="startDate"></fudis-datepicker>
* <fudis-datepicker fudisDateEnd [label]="'End'" [control]="endDate"></fudis-datepicker>
* </fudis-date-range>
* ```;
*/
class DateRangeComponent {
constructor(_idService) {
this._idService = _idService;
/**
* Internal date comparison parse. By setting to false date comparison parsing is not executed and
* comparison errors are not shown.
*/
this.dateComparisonParse = true;
/**
* Show date comparison errors
*/
this.showDateComparisonErrors = new BehaviorSubject(false);
}
ngOnChanges(changes) {
const dateComparisonParse = changes.dateComparisonParse?.currentValue;
const dateComparisonParseChanged = changes.dateComparisonParse?.currentValue !== changes.dateComparisonParse?.previousValue;
if (dateComparisonParseChanged) {
if (dateComparisonParse) {
this.checkDateCrossings();
}
else if (dateComparisonParse === false) {
this.showDateComparisonErrors.next(false);
}
}
}
/**
* Check if start date is set to after end date
*/
checkDateCrossings(value, type) {
if (value && type === 'start') {
this._startDateValue = value;
}
else if (value && type === 'end') {
this._endDateValue = value;
}
/* Show date comparison errors only if there are both date values and comparison is allowed.
* Compare only dates, do not take hours into account.
*/
if (this._startDateValue &&
this._endDateValue &&
this._startDateValue.setHours(0, 0, 0, 0) > this._endDateValue.setHours(0, 0, 0, 0) &&
this.dateComparisonParse) {
this.showDateComparisonErrors.next(true);
}
else if (this.showDateComparisonErrors.value !== false) {
this.showDateComparisonErrors.next(false);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DateRangeComponent, deps: [{ token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: DateRangeComponent, isStandalone: true, selector: "fudis-date-range", inputs: { dateComparisonParse: "dateComparisonParse" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"fudis-date-range\">\n <ng-content select=\"[fudisDateStart]\"></ng-content>\n <ng-content select=\"[fudisDateEnd]\"></ng-content>\n</div>\n", styles: [".fudis-date-range{display:flex;gap:calc(1rem / var(--fudis-rem-multiplier));align-items:baseline;opacity:1;max-width:calc(23rem / var(--fudis-rem-multiplier))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DateRangeComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-date-range', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fudis-date-range\">\n <ng-content select=\"[fudisDateStart]\"></ng-content>\n <ng-content select=\"[fudisDateEnd]\"></ng-content>\n</div>\n", styles: [".fudis-date-range{display:flex;gap:calc(1rem / var(--fudis-rem-multiplier));align-items:baseline;opacity:1;max-width:calc(23rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisIdService }], propDecorators: { dateComparisonParse: [{
type: Input
}] } });
/**
* Allows selection of a single date.
*
* Use this component for date input with calendar assistance and validation.
*/
class DatepickerComponent extends ControlComponentBaseDirective {
constructor(_parentDateRange, _adapter, _datePickerConfigService, _datepickerIntl, _translationService, _dialogService, _idService, _focusService) {
super(_idService, _focusService);
this._parentDateRange = _parentDateRange;
this._adapter = _adapter;
this._datePickerConfigService = _datePickerConfigService;
this._datepickerIntl = _datepickerIntl;
this._translationService = _translationService;
this._dialogService = _dialogService;
/**
* Datepicker size
*/
this.size = 'md';
/**
* Show internal date parsing validator message. By setting to false date parsing is not executed.
*/
this.dateParse = true;
/**
* Type of the Datepicker in Date Range
*/
this.dateRangeType = null;
/**
* Fudis translation for date parse error message
*/
this._dateParseError = new BehaviorSubject('');
/**
* Fudis translation for Datepicker placeholder text
*/
this._placeholderString = new BehaviorSubject('');
/**
* Signals reflecting the current state of the form control, updated on every control event.
*/
this._touched = signal(false, ...(ngDevMode ? [{ debugName: "_touched" }] : /* istanbul ignore next */ []));
this._invalid = signal(false, ...(ngDevMode ? [{ debugName: "_invalid" }] : /* istanbul ignore next */ []));
this._disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
/**
* Set and delete errors from Date Range start and end date inputs
*/
if (_parentDateRange) {
_parentDateRange?.showDateComparisonErrors.pipe(takeUntilDestroyed()).subscribe((value) => {
const currentErrors = this.control?.errors;
if (value) {
if (this.dateRangeType === 'start') {
this.control?.setErrors({
...currentErrors,
datepickerStartDateInvalid: { message: this._startDateInvalidTranslation },
});
}
else if (this.dateRangeType === 'end') {
this.control?.setErrors({
...currentErrors,
datepickerEndDateInvalid: { message: this._endDateInvalidTranslation },
});
}
this.control?.markAllAsTouched();
}
else {
if (currentErrors) {
delete currentErrors['datepickerStartDateInvalid'];
delete currentErrors['datepickerEndDateInvalid'];
this.control?.setErrors({ ...currentErrors });
this.control?.updateValueAndValidity();
}
}
});
}
this._updateValueAndValidityTrigger.pipe(takeUntilDestroyed()).subscribe(() => {
if (this.control) {
this._required.next(FudisValidatorUtilities.required(this.control));
this._minDate = FudisValidatorUtilities.minDate(this.control);
this._maxDate = FudisValidatorUtilities.maxDate(this.control);
}
});
effect(() => {
_adapter.setLocale(updateLocale(_translationService.getLanguageSignal()()));
const translations = _translationService.getTranslations()();
this._dateParseError.next(translations.DATEPICKER.VALIDATION.DATE_PARSE);
_datepickerIntl = updateMatDatePickerTranslations(translations, _datepickerIntl);
this._startDateInvalidTranslation =
this._translationService.getTranslations()().DATEPICKER.VALIDATION.START_DATE_INVALID;
this._endDateInvalidTranslation =
this._translationService.getTranslations()().DATEPICKER.VALIDATION.END_DATE_INVALID;
this._placeholderString.next(translations.DATEPICKER.PLACEHOLDER);
});
}
_syncControlState() {
this._touched.set(this.control.touched);
this._invalid.set(this.control.invalid);
this._disabled.set(this.control.disabled);
}
/**
* Validator reads HTML input field and checks if it can be converted to valid Date object
*/
_datepickerParseValidatorFn() {
return (control) => {
if (!control) {
return null;
}
const inputElValue = this._inputRef?.nativeElement?.value;
const isValidDate = inputElValue ? parseDate(inputElValue) : false;
if (!!inputElValue && !isValidDate) {
return { datepickerDateParse: { message: this._dateParseError } };
}
return null;
};
}
_addParseValidator() {
this._parseValidatorInstance = this._datepickerParseValidatorFn();
this.control.addValidators(this._parseValidatorInstance);
}
_removeParseValidator() {
if (this._parseValidatorInstance) {
this.control.removeValidators(this._parseValidatorInstance);
this._parseValidatorInstance = null;
}
}
_isCalendarOpen() {
return this._picker?.opened || false;
}
/**
* Handle calendar close
*/
_handleCalendarClose() {
this.control.updateValueAndValidity();
}
ngOnInit() {
this._setComponentId('datepicker');
if (!this._parseValidatorInstance && this.dateParse) {
this._addParseValidator();
}
}
ngAfterViewInit() {
super.ngAfterViewInit();
const inputElValue = this._inputRef?.nativeElement?.value;
const isValidDate = inputElValue ? parseDate(inputElValue) : false;
if (isValidDate !== false) {
this._parentDateRange?.checkDateCrossings(isValidDate, this.dateRangeType);
}
}
ngOnChanges(changes) {
// If prop dateParse value changes, add or remove validator accordingly
if (changes.dateParse?.currentValue !== changes.dateParse?.previousValue) {
if (changes.dateParse?.currentValue && !this._parseValidatorInstance) {
this._addParseValidator();
}
else if (!changes.dateParse?.currentValue && this._parseValidatorInstance) {
this._removeParseValidator();
}
}
// Do checks for the control to define attributes used in e.g. HTML
if (changes.control?.currentValue !== changes.control?.previousValue) {
this._subscription?.unsubscribe();
this._syncControlState();
this._updateValueAndValidityTrigger.next();
// Subscribe to control events to update the state signals and trigger value and validity updates
this._subscription = this.control.events
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => {
this._syncControlState();
this._updateValueAndValidityTrigger.next();
});
// Subscribe to control value changes and call parent's date crossing check with current value and Date Range input type
if (this._parentDateRange && this.dateRangeType) {
this._dateCrossingSubscription?.unsubscribe();
this._dateCrossingSubscription = this.control.valueChanges
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe((value) => {
this._parentDateRange?.checkDateCrossings(value, this.dateRangeType);
});
}
// If control changes and these checks are on, add parseValidator
if (!this._parseValidatorInstance && this.dateParse) {
this._addParseValidator();
}
}
}
ngOnDestroy() {
this._removeParseValidator();
}
/**
* When pressing keyboard Esc, close datepicker and set flag to indicate it was just closed with
* Escape key
*
* @param event
*/
_handleEscapePress(event) {
if (event.key === 'Escape' && this._isCalendarOpen()) {
event.preventDefault();
event.stopPropagation();
this._dialogService.dropdownClosedWithEscape();
this._picker.close();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DatepickerComponent, deps: [{ token: DateRangeComponent, host: true, optional: true }, { token: i2$1.DateAdapter }, { token: FudisTranslationService }, { token: i4.MatDatepickerIntl }, { token: FudisTranslationService }, { token: FudisDialogService }, { token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DatepickerComponent, isStandalone: true, selector: "fudis-datepicker", inputs: { control: "control", size: "size", dateParse: "dateParse", dateFilter: "dateFilter" }, host: { listeners: { "window:keydown": "_handleEscapePress($event)" } }, providers: [
{
provide: DateAdapter,
useClass: FudisDateAdapter,
deps: [MAT_DATE_LOCALE],
},
{ provide: MAT_DATE_FORMATS, useValue: FUDIS_DATE_FORMATS },
], viewQueries: [{ propertyName: "_picker", first: true, predicate: ["picker"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"fudis-datepicker fudis-input-size__{{ size }}\">\n <fudis-label\n [id]=\"id + '-label'\"\n [for]=\"id\"\n [text]=\"label\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n ></fudis-label>\n <div class=\"fudis-datepicker__input-wrapper\">\n <input\n #inputRef\n class=\"fudis-form-input fudis-datepicker__input\"\n [id]=\"id\"\n [matDatepicker]=\"picker\"\n [matDatepickerFilter]=\"dateFilter\"\n [formControl]=\"control\"\n (focus)=\"onFocus($event)\"\n (blur)=\"handleBlur.emit($event)\"\n (keyup)=\"handleKeyUp.emit($event)\"\n [min]=\"_minDate\"\n [max]=\"_maxDate\"\n [placeholder]=\"_placeholderString | async\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"_disabled() || disabled || null\"\n [attr.aria-invalid]=\"(_invalid() && _touched()) || null\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n />\n <mat-datepicker-toggle\n matSuffix\n class=\"fudis-datepicker__toggle\"\n [id]=\"id + '-calendar-icon-toggle'\"\n [for]=\"picker\"\n [disabled]=\"_disabled() || disabled\"\n [disableRipple]=\"true\"\n >\n <mat-icon matDatepickerToggleIcon>\n <fudis-icon\n [icon]=\"'calendar'\"\n [color]=\"_disabled() || disabled ? 'gray-dark' : 'primary'\"\n ></fudis-icon>\n </mat-icon>\n </mat-datepicker-toggle>\n <mat-datepicker\n #picker\n panelClass=\"fudis-datepicker__datepicker\"\n (closed)=\"_handleCalendarClose()\"\n ></mat-datepicker>\n </div>\n @if (!disableGuidance) {\n <fudis-guidance [inputLabel]=\"label\" [helpText]=\"helpText\" [for]=\"id\" [control]=\"control\">\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n</div>\n", styles: [".fudis-datepicker{width:100%}.fudis-datepicker__input-wrapper{position:relative}.fudis-datepicker__input{border:1px solid #484848;border-radius:2px;line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;box-sizing:border-box;padding:0 0 0 calc(.5rem / var(--fudis-rem-multiplier));width:100%;height:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-datepicker__input:focus{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-datepicker__toggle{display:flex;position:absolute;top:50%;right:calc(.25rem / var(--fudis-rem-multiplier));align-items:center;justify-content:center;transform:translateY(-50%)}.fudis-datepicker__datepicker{background-color:#fff;border:1px solid #484848}.fudis-datepicker__guidance__help-text{color:#484848;text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400;margin-top:calc(.5rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "component", type: LabelComponent, selector: "fudis-label", inputs: ["id", "text", "for", "required"] }, { kind: "directive", type: MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "directive", type: MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: MatDatepickerToggleIcon, selector: "[matDatepickerToggleIcon]" }, { kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }, { kind: "component", type: MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "component", type: GuidanceComponent, selector: "fudis-guidance", inputs: ["for", "inputLabel", "control", "formGroup", "helpText", "maxLength", "selectedOption", "groupBlurredOut", "groupHelpTextHidden"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DatepickerComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-datepicker', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
{
provide: DateAdapter,
useClass: FudisDateAdapter,
deps: [MAT_DATE_LOCALE],
},
{ provide: MAT_DATE_FORMATS, useValue: FUDIS_DATE_FORMATS },
], imports: [
LabelComponent,
MatDatepickerInput,
FormsModule,
ReactiveFormsModule,
MatDatepickerToggle,
MatSuffix,
MatDatepickerToggleIcon,
IconComponent,
MatDatepicker,
GuidanceComponent,
AsyncPipe,
], template: "<div class=\"fudis-datepicker fudis-input-size__{{ size }}\">\n <fudis-label\n [id]=\"id + '-label'\"\n [for]=\"id\"\n [text]=\"label\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n ></fudis-label>\n <div class=\"fudis-datepicker__input-wrapper\">\n <input\n #inputRef\n class=\"fudis-form-input fudis-datepicker__input\"\n [id]=\"id\"\n [matDatepicker]=\"picker\"\n [matDatepickerFilter]=\"dateFilter\"\n [formControl]=\"control\"\n (focus)=\"onFocus($event)\"\n (blur)=\"handleBlur.emit($event)\"\n (keyup)=\"handleKeyUp.emit($event)\"\n [min]=\"_minDate\"\n [max]=\"_maxDate\"\n [placeholder]=\"_placeholderString | async\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.aria-disabled]=\"_disabled() || disabled || null\"\n [attr.aria-invalid]=\"(_invalid() && _touched()) || null\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n />\n <mat-datepicker-toggle\n matSuffix\n class=\"fudis-datepicker__toggle\"\n [id]=\"id + '-calendar-icon-toggle'\"\n [for]=\"picker\"\n [disabled]=\"_disabled() || disabled\"\n [disableRipple]=\"true\"\n >\n <mat-icon matDatepickerToggleIcon>\n <fudis-icon\n [icon]=\"'calendar'\"\n [color]=\"_disabled() || disabled ? 'gray-dark' : 'primary'\"\n ></fudis-icon>\n </mat-icon>\n </mat-datepicker-toggle>\n <mat-datepicker\n #picker\n panelClass=\"fudis-datepicker__datepicker\"\n (closed)=\"_handleCalendarClose()\"\n ></mat-datepicker>\n </div>\n @if (!disableGuidance) {\n <fudis-guidance [inputLabel]=\"label\" [helpText]=\"helpText\" [for]=\"id\" [control]=\"control\">\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n</div>\n", styles: [".fudis-datepicker{width:100%}.fudis-datepicker__input-wrapper{position:relative}.fudis-datepicker__input{border:1px solid #484848;border-radius:2px;line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;box-sizing:border-box;padding:0 0 0 calc(.5rem / var(--fudis-rem-multiplier));width:100%;height:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-datepicker__input:focus{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-datepicker__toggle{display:flex;position:absolute;top:50%;right:calc(.25rem / var(--fudis-rem-multiplier));align-items:center;justify-content:center;transform:translateY(-50%)}.fudis-datepicker__datepicker{background-color:#fff;border:1px solid #484848}.fudis-datepicker__guidance__help-text{color:#484848;text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400;margin-top:calc(.5rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: DateRangeComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: i2$1.DateAdapter }, { type: FudisTranslationService }, { type: i4.MatDatepickerIntl }, { type: FudisTranslationService }, { type: FudisDialogService }, { type: FudisIdService }, { type: FudisFocusService }], propDecorators: { _picker: [{
type: ViewChild,
args: ['picker']
}], control: [{
type: Input,
args: [{ required: true }]
}], size: [{
type: Input
}], dateParse: [{
type: Input
}], dateFilter: [{
type: Input
}], _handleEscapePress: [{
type: HostListener,
args: ['window:keydown', ['$event']]
}] } });
/**
* Displays a collection of term-details pairs of related information.
*
* Use this component to present structured information with proper semantic relationships.
*/
class DescriptionListComponent extends GridApiDirective {
constructor(_idService) {
super();
this._idService = _idService;
/**
* Disable Grid behavior for Description List
*/
this.disableGrid = false;
/**
* Variant for Description List structure and layout: 'regular' | 'compact'
*/
this.variant = 'regular';
/**
* Grid row gap. Using Fudis spacing token values of xxs to xxl and none.
*/
this.rowGap = 'sm';
/**
* HTML element to render. If your Description List has only one item, use 'p', otherwise 'dl'.
*/
this.tag = 'dl';
/**
* CSS class list
*/
this._classList = new BehaviorSubject('');
/**
* Signal for variant
*/
this._dlVariant = signal('regular', ...(ngDevMode ? [{ debugName: "_dlVariant" }] : /* istanbul ignore next */ []));
/**
* Signal for listening disableGrid Input, used in DL Item.
*/
this._disabledGrid = signal(false, ...(ngDevMode ? [{ debugName: "_disabledGrid" }] : /* istanbul ignore next */ []));
this.id = this._idService.getNewGrandParentId('description-list');
}
ngOnInit() {
this._setClasses();
}
ngOnChanges(changes) {
if (changes.variant?.currentValue &&
changes.variant?.currentValue !== changes.variant?.previousValue) {
this._dlVariant.set(changes.variant.currentValue);
}
if (changes.disableGrid?.currentValue !== changes.disableGrid?.previousValue) {
this._setClasses();
const disableGrid = !!changes.disableGrid?.currentValue;
this._disabledGrid.set(disableGrid);
}
}
/**
* Read only signal for variant
*/
getVariant() {
return this._dlVariant.asReadonly();
}
/**
* Read only signal for disabledGrid value
*/
getDisabledGridStatus() {
return this._disabledGrid.asReadonly();
}
/**
* Define correct CSS classes
*/
_setClasses() {
const cssClasses = [];
cssClasses.push('fudis-dl');
if (this.disableGrid) {
cssClasses.push('fudis-dl__disabled-grid');
}
const combined = this.classes ? cssClasses.concat(this.classes) : cssClasses;
this._classList.next(combined.join(' '));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DescriptionListComponent, deps: [{ token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DescriptionListComponent, isStandalone: true, selector: "fudis-dl", inputs: { disableGrid: "disableGrid", variant: "variant", rowGap: "rowGap", tag: "tag" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "@if (tag === \"p\") {\n <ng-container *ngTemplateOutlet=\"singleItem\" />\n}\n\n@if (tag === \"dl\" && !disableGrid) {\n <ng-container *ngTemplateOutlet=\"multipleItems\" />\n}\n\n@if (tag === \"dl\" && disableGrid) {\n <ng-container *ngTemplateOutlet=\"disabledGrid\" />\n}\n\n<ng-template #disabledGrid>\n <dl [id]=\"id\" [class]=\"_classList | async\" role=\"list\">\n <ng-container *ngTemplateOutlet=\"dlContent\"></ng-container>\n </dl>\n</ng-template>\n\n<ng-template #singleItem>\n <div [id]=\"id\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"dlContent\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #multipleItems>\n @if (_classList | async; as classes) {\n <dl\n fudisGrid\n [id]=\"id\"\n [width]=\"width\"\n [align]=\"align\"\n [alignItemsX]=\"alignItemsX\"\n [alignItemsY]=\"alignItemsY\"\n [classes]=\"classes\"\n [serviceDefaults]=\"false\"\n [columnGap]=\"columnGap\"\n [rowGap]=\"variant === 'compact' ? 'none' : rowGap\"\n [columns]=\"columns ? columns : 2\"\n role=\"list\"\n >\n <ng-container *ngTemplateOutlet=\"dlContent\"></ng-container>\n </dl>\n }\n</ng-template>\n\n<ng-template #dlContent>\n <ng-content></ng-content>\n</ng-template>\n", styles: [".fudis-dl__disabled-grid{box-sizing:border-box;margin:0;padding:0;columns:1}@media screen and (min-width:48em){.fudis-dl__disabled-grid{columns:2}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: GridDirective, selector: "[fudisGrid]" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DescriptionListComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-dl', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, GridDirective, AsyncPipe], template: "@if (tag === \"p\") {\n <ng-container *ngTemplateOutlet=\"singleItem\" />\n}\n\n@if (tag === \"dl\" && !disableGrid) {\n <ng-container *ngTemplateOutlet=\"multipleItems\" />\n}\n\n@if (tag === \"dl\" && disableGrid) {\n <ng-container *ngTemplateOutlet=\"disabledGrid\" />\n}\n\n<ng-template #disabledGrid>\n <dl [id]=\"id\" [class]=\"_classList | async\" role=\"list\">\n <ng-container *ngTemplateOutlet=\"dlContent\"></ng-container>\n </dl>\n</ng-template>\n\n<ng-template #singleItem>\n <div [id]=\"id\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"dlContent\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #multipleItems>\n @if (_classList | async; as classes) {\n <dl\n fudisGrid\n [id]=\"id\"\n [width]=\"width\"\n [align]=\"align\"\n [alignItemsX]=\"alignItemsX\"\n [alignItemsY]=\"alignItemsY\"\n [classes]=\"classes\"\n [serviceDefaults]=\"false\"\n [columnGap]=\"columnGap\"\n [rowGap]=\"variant === 'compact' ? 'none' : rowGap\"\n [columns]=\"columns ? columns : 2\"\n role=\"list\"\n >\n <ng-container *ngTemplateOutlet=\"dlContent\"></ng-container>\n </dl>\n }\n</ng-template>\n\n<ng-template #dlContent>\n <ng-content></ng-content>\n</ng-template>\n", styles: [".fudis-dl__disabled-grid{box-sizing:border-box;margin:0;padding:0;columns:1}@media screen and (min-width:48em){.fudis-dl__disabled-grid{columns:2}}\n"] }]
}], ctorParameters: () => [{ type: FudisIdService }], propDecorators: { disableGrid: [{
type: Input
}], variant: [{
type: Input
}], rowGap: [{
type: Input
}], tag: [{
type: Input
}] } });
/**
* Represents a single term-detail pair.
*
* Use this component to group related description list terms and details together.
*/
class DescriptionListItemComponent {
constructor(_element, _idService, _parentDl) {
this._element = _element;
this._idService = _idService;
this._parentDl = _parentDl;
/**
* Main CSS class
*/
this._mainCssClass = new BehaviorSubject('fudis-dl-item');
/**
* Storing list of available languages in Details elements
*/
this._detailsLanguageOptions = signal(null, ...(ngDevMode ? [{ debugName: "_detailsLanguageOptions" }] : /* istanbul ignore next */ []));
/**
* Selected language to pass to child components
*/
this._selectedLanguage = signal(null, ...(ngDevMode ? [{ debugName: "_selectedLanguage" }] : /* istanbul ignore next */ []));
/**
* Current parent variant
*/
this._variant = new BehaviorSubject('regular');
/**
* Last Description List Item Detail id passed to children
*/
this._lastChildId = signal(null, ...(ngDevMode ? [{ debugName: "_lastChildId" }] : /* istanbul ignore next */ []));
this.id = _idService.getNewGroupId('description-list', this._parentDl.id);
toObservable(_parentDl.getVariant())
.pipe(takeUntilDestroyed())
.subscribe((newVariant) => {
if (newVariant === 'regular') {
this._variant.next('regular');
this._updateChildren();
}
else {
this._variant.next('compact');
this._updateChildren();
}
});
/**
* Listens to parent's changes and updates CSS classes.
*/
effect(() => {
const disabled = _parentDl.getDisabledGridStatus()();
this._setClasses(disabled, this._variant.value);
});
}
/**
* Returns a last child id of selected language.
*/
_findLastLanguageChildId(lang) {
const data = this._detailsLanguageOptions();
if (data && data[lang]) {
const keys = Object.keys(data[lang]);
return keys.length > 0 ? keys[keys.length - 1] : null;
}
return null;
}
/**
* Returns a compact list last child id for Description List Item Detail component.
*/
_updateChildren() {
if (this._variant.value !== 'compact') {
this._lastChildId.set(null);
return;
}
const selectedLang = this._selectedLanguage();
if (selectedLang) {
const lang = selectedLang;
const lastChildId = this._findLastLanguageChildId(lang);
this._lastChildId.set(lastChildId);
}
else {
this._detailChildrenIds = this._idService.getDlGrandChildrensIds('details', this._parentDl.id, this.id);
const lastChildId = this._detailChildrenIds?.length
? this._detailChildrenIds[this._detailChildrenIds.length - 1]
: null;
this._lastChildId.set(lastChildId);
}
}
/**
* DL Item has combined styles for both regular and compact versions but some styles only apply to
* regular version if parent's disableGrid is true.
*/
_setClasses(disabledGrid, parentVariant) {
if (disabledGrid && parentVariant !== 'compact') {
this._mainCssClass.next('fudis-dl-item__disabled-grid');
}
else {
this._mainCssClass.next('fudis-dl-item');
}
}
/**
* Called from child Details, if it has a language property
*/
addDetailsLanguage(lang, text, id) {
const currentContent = this._detailsLanguageOptions() || {};
if (!currentContent[lang]) {
currentContent[lang] = {};
}
currentContent[lang][id] = text;
this._detailsLanguageOptions.set({ ...currentContent });
}
/**
* Called from child Details, if its language property is removed (or updated)
*/
removeDetailsLanguage(lang, id) {
const currentContent = this._detailsLanguageOptions() || {};
if (currentContent[lang]?.[id]) {
delete currentContent[lang][id];
}
if (currentContent[lang] && Object.keys(currentContent[lang]).length === 0) {
delete currentContent[lang];
}
if (Object.keys(currentContent).length !== 0) {
this._detailsLanguageOptions.set({ ...currentContent });
}
else {
this._detailsLanguageOptions.set(null);
}
}
getDetailsLanguageOptions() {
return this._detailsLanguageOptions.asReadonly();
}
setSelectedLanguage(lang) {
this._selectedLanguage.set(lang);
this._updateChildren();
}
getSelectedLanguage() {
return this._selectedLanguage.asReadonly();
}
getLastChildId() {
return this._lastChildId.asReadonly();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DescriptionListItemComponent, deps: [{ token: i0.ElementRef }, { token: FudisIdService }, { token: DescriptionListComponent, host: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DescriptionListItemComponent, isStandalone: true, selector: "fudis-dl-item", ngImport: i0, template: "@if (_parentDl.tag === \"p\") {\n <ng-container *ngTemplateOutlet=\"singleItem\" />\n}\n\n@if (_parentDl.tag === \"dl\") {\n <ng-container *ngTemplateOutlet=\"multipleItems\" />\n}\n\n<ng-template #multipleItems>\n <div [id]=\"id\" [class]=\"_mainCssClass | async\" role=\"listitem\">\n <ng-container *ngTemplateOutlet=\"dlItemContent\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #singleItem>\n <p [id]=\"id\" [class]=\"_mainCssClass | async\">\n <ng-container *ngTemplateOutlet=\"dlItemContent\"></ng-container>\n </p>\n</ng-template>\n\n<ng-template #dlItemContent>\n <ng-content></ng-content>\n</ng-template>\n", styles: [".fudis-dl-item__disabled-grid{box-sizing:border-box;margin:0;padding:0;display:flex;flex-direction:column;justify-content:flex-start;margin-bottom:calc(1.5rem / var(--fudis-rem-multiplier));break-inside:avoid-column}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DescriptionListItemComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-dl-item', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, AsyncPipe], template: "@if (_parentDl.tag === \"p\") {\n <ng-container *ngTemplateOutlet=\"singleItem\" />\n}\n\n@if (_parentDl.tag === \"dl\") {\n <ng-container *ngTemplateOutlet=\"multipleItems\" />\n}\n\n<ng-template #multipleItems>\n <div [id]=\"id\" [class]=\"_mainCssClass | async\" role=\"listitem\">\n <ng-container *ngTemplateOutlet=\"dlItemContent\"></ng-container>\n </div>\n</ng-template>\n\n<ng-template #singleItem>\n <p [id]=\"id\" [class]=\"_mainCssClass | async\">\n <ng-container *ngTemplateOutlet=\"dlItemContent\"></ng-container>\n </p>\n</ng-template>\n\n<ng-template #dlItemContent>\n <ng-content></ng-content>\n</ng-template>\n", styles: [".fudis-dl-item__disabled-grid{box-sizing:border-box;margin:0;padding:0;display:flex;flex-direction:column;justify-content:flex-start;margin-bottom:calc(1.5rem / var(--fudis-rem-multiplier));break-inside:avoid-column}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: FudisIdService }, { type: DescriptionListComponent, decorators: [{
type: Host
}] }] });
/**
* Displays the details (value) of a term (key) in a DescriptionListItemComponent.
*/
class DescriptionListItemDetailsComponent {
constructor(_elementRef, _idService, _translationService, _parentDlItem, _parentDl) {
this._elementRef = _elementRef;
this._idService = _idService;
this._translationService = _translationService;
this._parentDlItem = _parentDlItem;
this._parentDl = _parentDl;
/**
* Binding host CSS class to component wrapper
*/
this.hostClass = 'fudis-dl-item-details-host';
/**
* Show empty state content
*/
this.emptyState = false;
/**
* Selected language to show respective Details content
*/
this._langSelected = new BehaviorSubject(false);
/**
* If current Detail element is a last child of it's parent
*/
this._lastChild = new BehaviorSubject(false);
/**
* Main CSS class
*/
this._mainCssClass = new BehaviorSubject('fudis-dl-item-details__regular');
this._id = this._idService.getNewDlGrandChilId('details', this._parentDl.id, this._parentDlItem.id);
toObservable(_parentDl.getVariant())
.pipe(takeUntilDestroyed())
.subscribe((newVariant) => {
if (newVariant === 'regular') {
this._mainCssClass.next('fudis-dl-item-details__regular');
}
else {
this._mainCssClass.next('fudis-dl-item-details__compact');
}
});
toObservable(_parentDlItem.getLastChildId())
.pipe(takeUntilDestroyed())
.subscribe((lastCompactId) => {
if (this._mainCssClass.value !== 'fudis-dl-item-details__compact') {
this._lastChild.next(false);
}
else {
this._lastChild.next(lastCompactId !== this._id);
}
});
toObservable(_parentDlItem.getSelectedLanguage())
.pipe(takeUntilDestroyed())
.subscribe((newLang) => {
this._langSelected.next(!!(this.lang && newLang === this.lang));
});
}
/**
* Parse Details text content and set parent Description List Item languages
*/
_sendDetailsLanguageToParent() {
const parsedContentText = this.contentText && this.contentText.replace(/\s/g, '') !== '' ? this.contentText : null;
if (parsedContentText && !this._detailsSent) {
this._parentDlItem.addDetailsLanguage(this.lang, parsedContentText, this._id);
this._detailsSent = true;
this.hostClass = `fudis-dl-item-details-host fudis-dl-item-details-host--${this.lang}`;
}
}
_removeDetailsFromParent() {
if (this._detailsSent) {
this._parentDlItem.removeDetailsLanguage(this.lang, this._id);
this._detailsSent = false;
this.hostClass = `fudis-dl-item-details-host`;
}
}
ngOnChanges(changes) {
// If language changes, update it to parent. If language changes to undefined, remove it from parent.
if (!changes.lang?.firstChange && !!changes.lang?.currentValue) {
this._removeDetailsFromParent();
this._sendDetailsLanguageToParent();
}
else if (!changes.lang?.currentValue) {
this._removeDetailsFromParent();
}
// If text content changes, update it to parent
if (changes.contentText?.currentValue !== changes.contentText?.previousValue && this.lang) {
this._sendDetailsLanguageToParent();
}
}
ngOnDestroy() {
this._removeDetailsFromParent();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DescriptionListItemDetailsComponent, deps: [{ token: i0.ElementRef }, { token: FudisIdService }, { token: FudisTranslationService }, { token: DescriptionListItemComponent, host: true }, { token: DescriptionListComponent, host: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DescriptionListItemDetailsComponent, isStandalone: true, selector: "fudis-dd", inputs: { lang: "lang", contentText: "contentText", subHeading: "subHeading", ariaLabel: "ariaLabel", emptyState: "emptyState", emptyStateContentText: "emptyStateContentText" }, host: { properties: { "class": "this.hostClass" } }, usesOnChanges: true, ngImport: i0, template: "@if (!lang || (_langSelected | async)) {\n @if (_parentDl.tag === \"p\") {\n <span [id]=\"_id\" [attr.lang]=\"lang\" [class]=\"_mainCssClass | async\">\n <ng-container *ngTemplateOutlet=\"ddContent\"></ng-container>\n </span>\n }\n @if (_parentDl.tag === \"dl\") {\n <dd [id]=\"_id\" [attr.lang]=\"lang\" [class]=\"_mainCssClass | async\">\n @if (!emptyState) {\n <ng-container *ngTemplateOutlet=\"ddContent\"></ng-container>\n } @else {\n <span class=\"fudis-text-emptystate\">{{\n emptyStateContentText ||\n _translationService.getTranslations()().DESCRIPTION_LIST_ITEM_DETAILS.EMPTY_STATE\n }}</span>\n }\n </dd>\n }\n}\n\n<ng-template #ddContent>\n @if (subHeading) {\n <span [class]=\"(_mainCssClass | async) + '__sub-heading'\">{{ subHeading }}</span>\n }\n <span [class]=\"(_mainCssClass | async) + '__content'\" [attr.aria-label]=\"ariaLabel\">\n @if (subHeading) {\n <span [class]=\"(_mainCssClass | async) + '__parenthesis'\"> (</span>\n }\n <span [attr.aria-hidden]=\"ariaLabel ? true : null\">{{ contentText }}</span>\n @if (subHeading) {\n <span [class]=\"(_mainCssClass | async) + '__parenthesis'\">)</span>\n }\n <ng-content\n /></span>\n @if (contentText && (_lastChild | async)) {\n <span [class]=\"(_mainCssClass | async) + '__comma'\" aria-hidden=\"true\">,</span>\n }\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DescriptionListItemDetailsComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-dd', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, AsyncPipe], template: "@if (!lang || (_langSelected | async)) {\n @if (_parentDl.tag === \"p\") {\n <span [id]=\"_id\" [attr.lang]=\"lang\" [class]=\"_mainCssClass | async\">\n <ng-container *ngTemplateOutlet=\"ddContent\"></ng-container>\n </span>\n }\n @if (_parentDl.tag === \"dl\") {\n <dd [id]=\"_id\" [attr.lang]=\"lang\" [class]=\"_mainCssClass | async\">\n @if (!emptyState) {\n <ng-container *ngTemplateOutlet=\"ddContent\"></ng-container>\n } @else {\n <span class=\"fudis-text-emptystate\">{{\n emptyStateContentText ||\n _translationService.getTranslations()().DESCRIPTION_LIST_ITEM_DETAILS.EMPTY_STATE\n }}</span>\n }\n </dd>\n }\n}\n\n<ng-template #ddContent>\n @if (subHeading) {\n <span [class]=\"(_mainCssClass | async) + '__sub-heading'\">{{ subHeading }}</span>\n }\n <span [class]=\"(_mainCssClass | async) + '__content'\" [attr.aria-label]=\"ariaLabel\">\n @if (subHeading) {\n <span [class]=\"(_mainCssClass | async) + '__parenthesis'\"> (</span>\n }\n <span [attr.aria-hidden]=\"ariaLabel ? true : null\">{{ contentText }}</span>\n @if (subHeading) {\n <span [class]=\"(_mainCssClass | async) + '__parenthesis'\">)</span>\n }\n <ng-content\n /></span>\n @if (contentText && (_lastChild | async)) {\n <span [class]=\"(_mainCssClass | async) + '__comma'\" aria-hidden=\"true\">,</span>\n }\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: FudisIdService }, { type: FudisTranslationService }, { type: DescriptionListItemComponent, decorators: [{
type: Host
}] }, { type: DescriptionListComponent, decorators: [{
type: Host
}] }], propDecorators: { hostClass: [{
type: HostBinding,
args: ['class']
}], lang: [{
type: Input
}], contentText: [{
type: Input
}], subHeading: [{
type: Input
}], ariaLabel: [{
type: Input
}], emptyState: [{
type: Input
}], emptyStateContentText: [{
type: Input
}] } });
class LanguageBadgeComponent {
constructor(_translationService, _idService) {
this._translationService = _translationService;
this._idService = _idService;
/*
* Language Badge variant
*/
this.variant = 'standard';
/**
* Click handler outputting clicked language abbreviation
*/
this.handleClick = new EventEmitter();
/**
* Label string of the badge
*/
this._badgeLabel = new BehaviorSubject('');
effect(() => {
const translations = this._translationService.getTranslations()();
this._selectedTranslation = translations.LANGUAGE_BADGE.ARIA_LABEL.SELECTED;
this._missingTranslation = translations.LANGUAGE_BADGE.ARIA_LABEL.MISSING_TRANSLATION;
this._labelTranslations = translations.LANGUAGE_BADGE.ARIA_LABEL;
this._setLabel();
});
}
ngOnInit() {
this._id = this._idService.getNewChildId('language-badge-group', this.parentId);
}
ngOnChanges(changes) {
if (changes.selected?.currentValue !== changes.selected?.previousValue ||
changes.variant?.currentValue !== changes.variant?.previousValue ||
changes.language?.currentValue !== changes.language?.previousValue) {
if (this._labelTranslations && this._selectedTranslation && this._missingTranslation) {
this._setLabel();
}
}
}
/**
* Emit badge's language on click
*/
_handleLanguageSelect() {
this.handleClick.emit(this.language);
}
/**
* Define correct label for Badge
*/
_setLabel() {
let newLabel;
const keyValue = this.language.toUpperCase();
const label = this._labelTranslations[keyValue];
if (this.selected && this.variant !== 'missing') {
newLabel = `${label} ${this._selectedTranslation}`;
}
else if (this.selected && this.variant === 'missing') {
newLabel = `${label} ${this._missingTranslation} ${this._selectedTranslation}`;
}
else if (!this.selected && this.variant === 'missing') {
newLabel = `${label} ${this._missingTranslation}`;
}
else {
newLabel = label;
}
this._badgeLabel.next(newLabel);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LanguageBadgeComponent, deps: [{ token: FudisTranslationService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: LanguageBadgeComponent, isStandalone: true, selector: "fudis-language-badge", inputs: { language: "language", parentId: "parentId", selected: "selected", variant: "variant", ariaLabel: "ariaLabel" }, outputs: { handleClick: "handleClick" }, usesOnChanges: true, ngImport: i0, template: "@if (_badgeLabel | async; as badgeLabel) {\n <button\n type=\"button\"\n class=\"fudis-language-badge\"\n [attr.aria-label]=\"badgeLabel\"\n [attr.id]=\"_id\"\n (click)=\"_handleLanguageSelect()\"\n >\n <span\n [class.fudis-language-badge--selected]=\"selected\"\n class=\"fudis-language-badge__content fudis-language-badge__{{ variant }}\"\n >{{ language }}</span\n >\n </button>\n}\n", styles: [".fudis-language-badge{background-color:transparent;border:none;box-sizing:border-box;margin:0;padding:0;display:inline-flex;align-items:center;justify-content:center;width:calc(2rem / var(--fudis-rem-multiplier));height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-language-badge:focus-within{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-language-badge__content{text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400;background-color:#fff;border:1px solid #484848;box-sizing:border-box;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border-radius:50%;min-width:calc(1.5rem / var(--fudis-rem-multiplier));min-height:calc(1.5rem / var(--fudis-rem-multiplier));text-align:center;letter-spacing:calc(.02rem / var(--fudis-rem-multiplier))}.fudis-language-badge__missing{border:1px dashed #484848}.fudis-language-badge--selected,.fudis-language-badge--selected:focus{outline:2px solid var(--fudis-color-primary);outline-offset:-2px}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LanguageBadgeComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-language-badge', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AsyncPipe], template: "@if (_badgeLabel | async; as badgeLabel) {\n <button\n type=\"button\"\n class=\"fudis-language-badge\"\n [attr.aria-label]=\"badgeLabel\"\n [attr.id]=\"_id\"\n (click)=\"_handleLanguageSelect()\"\n >\n <span\n [class.fudis-language-badge--selected]=\"selected\"\n class=\"fudis-language-badge__content fudis-language-badge__{{ variant }}\"\n >{{ language }}</span\n >\n </button>\n}\n", styles: [".fudis-language-badge{background-color:transparent;border:none;box-sizing:border-box;margin:0;padding:0;display:inline-flex;align-items:center;justify-content:center;width:calc(2rem / var(--fudis-rem-multiplier));height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-language-badge:focus-within{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-language-badge__content{text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400;background-color:#fff;border:1px solid #484848;box-sizing:border-box;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border-radius:50%;min-width:calc(1.5rem / var(--fudis-rem-multiplier));min-height:calc(1.5rem / var(--fudis-rem-multiplier));text-align:center;letter-spacing:calc(.02rem / var(--fudis-rem-multiplier))}.fudis-language-badge__missing{border:1px dashed #484848}.fudis-language-badge--selected,.fudis-language-badge--selected:focus{outline:2px solid var(--fudis-color-primary);outline-offset:-2px}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: FudisIdService }], propDecorators: { language: [{
type: Input,
args: [{ required: true }]
}], parentId: [{
type: Input,
args: [{ required: true }]
}], selected: [{
type: Input
}], variant: [{
type: Input
}], ariaLabel: [{
type: Input
}], handleClick: [{
type: Output
}] } });
/**
* Displays a group of language indicators.
*
* Use this component to present available or active languages for content.
*/
class LanguageBadgeGroupComponent extends PopoverApiDirective {
constructor(_translationService, _idService) {
super();
this._translationService = _translationService;
this._idService = _idService;
/**
* Output Language abbreviation of clicked Badge
*/
this.handleClick = new EventEmitter();
/**
* Translation for aria-label
*/
this._groupAriaLabel = new BehaviorSubject('');
/**
* Internal variable for matching languages and label texts
*/
this._languageLabels = new BehaviorSubject([]);
this._selectedLanguage = null;
this._id = _idService.getNewParentId('language-badge-group');
effect(() => {
this._groupAriaLabel.next(_translationService.getTranslations()().LANGUAGE_BADGE.ARIA_LABEL.TRANSLATIONS);
});
effect(() => {
this._selectableAppLanguages = _translationService.getSelectableLanguages()();
this._currentAppLang = _translationService.getLanguageSignal()();
if (this.translatedLanguages) {
this._setLanguageOptions(this.translatedLanguages);
this._determineSelectedBadge(this.translatedLanguages);
}
});
}
/**
* Set selected language and emits clicked language
*/
_updateLanguage(value) {
this._selectedLanguage = value;
this.handleClick.emit(value);
}
/**
* Creates an array to loop in template of wanted Language Badges
*/
_setLanguageOptions(availableContent) {
const tempLangLabels = [];
this._selectableAppLanguages?.forEach((language) => {
const variant = availableContent.includes(language) ? 'standard' : 'missing';
const newItem = { key: language, variant };
tempLangLabels.push(newItem);
});
this._languageLabels.next(tempLangLabels);
}
_determineSelectedBadge(translatedLanguages) {
let determinedLanguage;
if (translatedLanguages?.includes(this._currentAppLang) &&
this._selectableAppLanguages.includes(this._currentAppLang)) {
determinedLanguage = this._currentAppLang;
}
else {
const firstAvailable = this._selectableAppLanguages.find((lang) => translatedLanguages.includes(lang));
if (firstAvailable) {
determinedLanguage = firstAvailable;
}
else {
determinedLanguage = null;
}
}
this._updateLanguage(determinedLanguage);
}
ngOnChanges(changes) {
const currentTranslated = changes.translatedLanguages?.currentValue;
if (currentTranslated && changes.translatedLanguages?.previousValue !== currentTranslated) {
this._setLanguageOptions(currentTranslated);
}
if (changes.translatedLanguages?.isFirstChange() &&
currentTranslated &&
this._selectableAppLanguages &&
this._currentAppLang) {
this._determineSelectedBadge(currentTranslated);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LanguageBadgeGroupComponent, deps: [{ token: FudisTranslationService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: LanguageBadgeGroupComponent, isStandalone: true, selector: "fudis-language-badge-group", inputs: { translatedLanguages: "translatedLanguages" }, outputs: { handleClick: "handleClick" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<section\n [attr.id]=\"_id\"\n [attr.aria-label]=\"_groupAriaLabel | async\"\n class=\"fudis-language-badge-group\"\n>\n @for (_language of _languageLabels | async; track _language.key; let index = $index) {\n <fudis-language-badge\n [parentId]=\"_id\"\n (handleClick)=\"_updateLanguage($event)\"\n [selected]=\"_selectedLanguage === _language.key\"\n [language]=\"_language.key\"\n [variant]=\"_language.variant\"\n />\n }\n</section>\n", styles: [".fudis-language-badge-group{display:inline-flex;align-items:center;margin-left:calc(.25rem / var(--fudis-rem-multiplier));height:calc(1.5rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "component", type: LanguageBadgeComponent, selector: "fudis-language-badge", inputs: ["language", "parentId", "selected", "variant", "ariaLabel"], outputs: ["handleClick"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LanguageBadgeGroupComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-language-badge-group', changeDetection: ChangeDetectionStrategy.OnPush, imports: [LanguageBadgeComponent, AsyncPipe], template: "<section\n [attr.id]=\"_id\"\n [attr.aria-label]=\"_groupAriaLabel | async\"\n class=\"fudis-language-badge-group\"\n>\n @for (_language of _languageLabels | async; track _language.key; let index = $index) {\n <fudis-language-badge\n [parentId]=\"_id\"\n (handleClick)=\"_updateLanguage($event)\"\n [selected]=\"_selectedLanguage === _language.key\"\n [language]=\"_language.key\"\n [variant]=\"_language.variant\"\n />\n }\n</section>\n", styles: [".fudis-language-badge-group{display:inline-flex;align-items:center;margin-left:calc(.25rem / var(--fudis-rem-multiplier));height:calc(1.5rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: FudisIdService }], propDecorators: { translatedLanguages: [{
type: Input,
args: [{ required: true }]
}], handleClick: [{
type: Output
}] } });
/**
* Displays the term (key) in a DescriptionListItemComponent.
*/
class DescriptionListItemTermComponent extends PopoverApiDirective {
constructor(_translationService, _idService, _parentDlItem, _parentDl) {
super();
this._translationService = _translationService;
this._idService = _idService;
this._parentDlItem = _parentDlItem;
this._parentDl = _parentDl;
/**
* Renders Fudis Language Badge Group Component for displaying Description List Item Detail values
* in given languages
*/
this.languages = true;
/**
* Available translated languages of sibling dt elements fetched from the parent dl-item element
*/
this._parentLanguageTranslations = signal([], ...(ngDevMode ? [{ debugName: "_parentLanguageTranslations" }] : /* istanbul ignore next */ []));
/**
* Main CSS class
*/
this._mainCssClass = new BehaviorSubject('fudis-dl-item-term__regular');
this._id = this._idService.getNewDlGrandChilId('term', this._parentDl.id, this._parentDlItem.id);
effect(() => {
if (_parentDl.getVariant()() === 'regular') {
this._mainCssClass.next('fudis-dl-item-term__regular');
}
else {
this._mainCssClass.next('fudis-dl-item-term__compact');
}
});
effect(() => {
const parentLanguageOptions = _parentDlItem.getDetailsLanguageOptions()();
if (parentLanguageOptions) {
const translated = [];
for (const [key, value] of Object.entries(parentLanguageOptions)) {
if (Object.keys(value).some((itemId) => value[itemId] !== null)) {
translated.push(key);
}
}
this._parentLanguageTranslations.set(translated);
}
});
}
/**
* When Language Badge is clicked, update clicked language to parent item
*
* @param lang FudisLanguageAbbr
*/
_setSelectedLanguage(lang) {
if (this.languages) {
this._parentDlItem.setSelectedLanguage(lang);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DescriptionListItemTermComponent, deps: [{ token: FudisTranslationService }, { token: FudisIdService }, { token: DescriptionListItemComponent, host: true }, { token: DescriptionListComponent, host: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DescriptionListItemTermComponent, isStandalone: true, selector: "fudis-dt", inputs: { languages: "languages", contentText: "contentText" }, usesInheritance: true, ngImport: i0, template: "@if (_parentDl.tag === \"dl\") {\n <ng-container *ngTemplateOutlet=\"multipleItems\" />\n}\n\n@if (_parentDl.tag === \"p\") {\n <ng-container *ngTemplateOutlet=\"singleItem\" />\n}\n\n<ng-template #multipleItems>\n <dt [id]=\"_id\" [class]=\"_mainCssClass | async\">\n <ng-container *ngTemplateOutlet=\"commonContent\"></ng-container>\n </dt>\n</ng-template>\n\n<ng-template #singleItem>\n <span [id]=\"_id\" [class]=\"_mainCssClass | async\">\n <ng-container *ngTemplateOutlet=\"commonContent\"></ng-container>\n </span>\n</ng-template>\n\n<ng-template #commonContent>\n <span [class]=\"(_mainCssClass | async) + '__text-content'\">{{ contentText }}</span>\n @if (_mainCssClass.value === \"fudis-dl-item-term__compact\") {\n <span class=\"fudis-dl-item-term__compact__colon\">:</span>\n }\n @if (popoverTriggerLabel && popoverText) {\n <span [class]=\"(_mainCssClass | async) + '__popover'\">\n <fudis-icon-button\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'info-circle'\"\n [ariaLabel]=\"popoverTriggerLabel\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n />\n </span>\n }\n\n @if (languages && _parentLanguageTranslations().length > 0) {\n <fudis-language-badge-group\n [translatedLanguages]=\"_parentLanguageTranslations()\"\n (handleClick)=\"_setSelectedLanguage($event)\"\n />\n }\n</ng-template>\n", styles: [".fudis-dl-item-term__regular__popover{display:inline-flex;align-items:center;margin-left:calc(.25rem / var(--fudis-rem-multiplier));height:calc(1.5rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconButtonComponent, selector: "fudis-icon-button", inputs: ["ariaLabel", "icon", "asMenuButton"] }, { kind: "component", type: LanguageBadgeGroupComponent, selector: "fudis-language-badge-group", inputs: ["translatedLanguages"], outputs: ["handleClick"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DescriptionListItemTermComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-dt', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, IconButtonComponent, LanguageBadgeGroupComponent, AsyncPipe], template: "@if (_parentDl.tag === \"dl\") {\n <ng-container *ngTemplateOutlet=\"multipleItems\" />\n}\n\n@if (_parentDl.tag === \"p\") {\n <ng-container *ngTemplateOutlet=\"singleItem\" />\n}\n\n<ng-template #multipleItems>\n <dt [id]=\"_id\" [class]=\"_mainCssClass | async\">\n <ng-container *ngTemplateOutlet=\"commonContent\"></ng-container>\n </dt>\n</ng-template>\n\n<ng-template #singleItem>\n <span [id]=\"_id\" [class]=\"_mainCssClass | async\">\n <ng-container *ngTemplateOutlet=\"commonContent\"></ng-container>\n </span>\n</ng-template>\n\n<ng-template #commonContent>\n <span [class]=\"(_mainCssClass | async) + '__text-content'\">{{ contentText }}</span>\n @if (_mainCssClass.value === \"fudis-dl-item-term__compact\") {\n <span class=\"fudis-dl-item-term__compact__colon\">:</span>\n }\n @if (popoverTriggerLabel && popoverText) {\n <span [class]=\"(_mainCssClass | async) + '__popover'\">\n <fudis-icon-button\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'info-circle'\"\n [ariaLabel]=\"popoverTriggerLabel\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n />\n </span>\n }\n\n @if (languages && _parentLanguageTranslations().length > 0) {\n <fudis-language-badge-group\n [translatedLanguages]=\"_parentLanguageTranslations()\"\n (handleClick)=\"_setSelectedLanguage($event)\"\n />\n }\n</ng-template>\n", styles: [".fudis-dl-item-term__regular__popover{display:inline-flex;align-items:center;margin-left:calc(.25rem / var(--fudis-rem-multiplier));height:calc(1.5rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: FudisIdService }, { type: DescriptionListItemComponent, decorators: [{
type: Host
}] }, { type: DescriptionListComponent, decorators: [{
type: Host
}] }], propDecorators: { languages: [{
type: Input
}], contentText: [{
type: Input
}] } });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const throttle = (f, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => f.apply(this, args), delay);
};
};
/**
* Here we check if the button element is disabled. If it is, close action is not triggered. In case
* of a _elementRef, it takes the exact element that is attached to, so if it is a fudis-button, we
* have to check the underlying button element with firstElementChild, that has the aria-disabled
* attribute. If it is just a button, checking the passed element is enough.
*/
function isButtonDisabled(element) {
return ((element?.ariaDisabled && element?.ariaDisabled === 'true') ||
(element?.firstElementChild?.ariaDisabled &&
element?.firstElementChild?.ariaDisabled === 'true'));
}
/**
* Identifies the title of a dialog.
*
* Use this directive to provide automatically focused heading when opening a dialog.
*/
class DialogTitleDirective extends MatDialogTitle {
constructor(
// For MatDialogTitle
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_dialogRef, _elementRef, _dialog,
// For Fudis DialogTitleDirective
_headingElementRef, _renderer) {
super(_dialogRef, _elementRef, _dialog);
this._headingElementRef = _headingElementRef;
this._renderer = _renderer;
}
ngOnInit() {
this._renderer.setAttribute(this._headingElementRef.nativeElement, 'tabindex', '-1');
this._headingElementRef.nativeElement.focus();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogTitleDirective, deps: [{ token: i1.MatDialogRef }, { token: i0.ElementRef }, { token: i1.MatDialog }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: DialogTitleDirective, isStandalone: true, selector: "[fudisDialogTitle]", providers: [
{
provide: MatDialogRef,
useValue: {},
},
], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogTitleDirective, decorators: [{
type: Directive,
args: [{
selector: '[fudisDialogTitle]',
providers: [
{
provide: MatDialogRef,
useValue: {},
},
],
}]
}], ctorParameters: () => [{ type: i1.MatDialogRef }, { type: i0.ElementRef }, { type: i1.MatDialog }, { type: i0.ElementRef }, { type: i0.Renderer2 }] });
/**
* Identifies the main content area of a dialog.
*/
class DialogContentDirective extends MatDialogContent {
constructor(_elRef, _renderer) {
super();
this._elRef = _elRef;
this._renderer = _renderer;
/**
* Binding fudis-dialog-content CSS class to directive
*/
this.hostClass = 'fudis-dialog-content';
/**
* Dialog contentFocus sets a visual focus to dialog content. Preferred to be used with read-only
* content.
*/
this.contentFocus = false;
}
ngOnChanges(changes) {
const contentFocus = changes.contentFocus?.currentValue;
if (contentFocus !== changes.contentFocus?.previousValue) {
if (contentFocus) {
this._renderer.setAttribute(this._elRef.nativeElement, 'tabindex', '0');
this._renderer.setAttribute(this._elRef.nativeElement, 'role', 'document');
}
else {
this._renderer.removeAttribute(this._elRef.nativeElement, 'tabindex');
this._renderer.removeAttribute(this._elRef.nativeElement, 'role');
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogContentDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: DialogContentDirective, isStandalone: true, selector: "fudis-dialog-content", inputs: { contentFocus: "contentFocus" }, host: { properties: { "class": "this.hostClass" } }, usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogContentDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-dialog-content' }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { hostClass: [{
type: HostBinding,
args: ['class']
}], contentFocus: [{
type: Input
}] } });
/**
* Identifies the actions region of a dialog.
*
* Use this directive to set action buttons in a fixed position at the bottom of a dialog.
*/
class DialogActionsDirective extends MatDialogActions {
constructor() {
super(...arguments);
this.align = 'end';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogActionsDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: DialogActionsDirective, isStandalone: true, selector: "fudis-dialog-actions", usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogActionsDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-dialog-actions' }]
}] });
/**
* Marks and element that closes a dialog.
*
* Use this directive to provide a button which solely dismisses a dialog.
*/
class DialogCloseDirective extends MatDialogClose {
constructor(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_dialogRef, _elementRef, _dialog) {
super(_dialogRef, _elementRef, _dialog);
this._hostButtonElement = _elementRef?.nativeElement;
}
_onButtonClick() {
if (!isButtonDisabled(this._hostButtonElement))
this.dialogRef.close();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogCloseDirective, deps: [{ token: i1.MatDialogRef }, { token: i0.ElementRef }, { token: i1.MatDialog }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: DialogCloseDirective, isStandalone: true, selector: "[fudisDialogClose]", usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogCloseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisDialogClose]' }]
}], ctorParameters: () => [{ type: i1.MatDialogRef }, { type: i0.ElementRef }, { type: i1.MatDialog }] });
/**
* Displays content in a modal overlay.
*
* Must be opened via `FudisDialogService.open(Component)` or `FudisDialogService.open(TemplateRef)`
* — not used as inline HTML. Use content directive `fudisDialogTitle` as inline attribute for the
* dialog heading. Use `<fudis-dialog-content>` and `<fudis-dialog-actions>` directives as HTML
* elements inside the dialog template. Add `fudisDialogClose` directive on a button to close it.
*
* Use this component for focused interactions that require user attention or confirmation before
* continuing.
*/
class DialogComponent {
constructor(_translateService, _dialogService, _idService, _elementRef) {
this._translateService = _translateService;
this._dialogService = _dialogService;
this._idService = _idService;
this._elementRef = _elementRef;
/**
* Dialog size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
*/
this.size = 'md';
/**
* Dialog's close button has to have absolute positioning when used inside fudis-form
*/
this.closeButtonPositionAbsolute = signal(false, ...(ngDevMode ? [{ debugName: "closeButtonPositionAbsolute" }] : /* istanbul ignore next */ []));
/**
* Does Dialog have scrollable content
*/
this._dialogScrollable = signal(null, ...(ngDevMode ? [{ debugName: "_dialogScrollable" }] : /* istanbul ignore next */ []));
this._id = _idService.getNewId('dialog');
_dialogService.setDialogOpenStatus(true);
this._resizeObserver = new ResizeObserver(throttle(() => {
if (this._dialogScrollable() !== null) {
this._isDialogScrollable();
}
}, 10));
fromEvent(window, 'resize')
.pipe(takeUntilDestroyed(), debounceTime(10))
.subscribe(() => {
if (this._dialogScrollable() !== null) {
this._isDialogScrollable();
}
});
}
ngOnChanges(changes) {
if (changes.size?.currentValue !== changes.size?.previousValue) {
this._isDialogScrollable();
}
}
ngOnInit() {
this._orderNumber = this._dialogService.dialogsOpen().length;
}
ngAfterViewInit() {
this._isDialogScrollable();
}
ngOnDestroy() {
if (this._orderNumber === 1 || this._orderNumber === 0) {
this._dialogService.setDialogOpenStatus(false);
}
this._resizeObserver.disconnect();
}
/**
* From: https://phuoc.ng/collection/html-dom/check-if-an-element-is-scrollable/
*/
_isDialogScrollable() {
const dialogContentElement = this._elementRef?.nativeElement?.querySelector('.fudis-dialog-content');
const dialogContentVisible = dialogContentElement?.clientHeight;
const dialogContentScrollable = dialogContentElement?.scrollHeight;
const formContent = this._elementRef?.nativeElement?.querySelector('.fudis-form-content')?.clientHeight;
const formScrollableContent = this._elementRef?.nativeElement?.querySelector('.fudis-form__content-wrapper')?.clientHeight;
// Compare the height to see if the element has scrollable content
const hasScrollableContent = (formScrollableContent && formContent && formContent > formScrollableContent) ||
(dialogContentScrollable &&
dialogContentVisible &&
dialogContentScrollable > dialogContentVisible);
// It's not enough because the element's `overflow-y` style can be set as
// * `hidden`
// * `hidden !important`
// In those cases, the scrollbar isn't shown
const overflowYStyle = window.getComputedStyle(this._elementRef.nativeElement).overflowY;
const isOverflowHidden = overflowYStyle.indexOf('hidden') !== -1;
if (hasScrollableContent && !isOverflowHidden) {
this._dialogScrollable.set(true);
}
else {
this._dialogScrollable.set(false);
}
}
_handleEscapePress(event) {
if (event.key === 'Escape') {
if (this._dialogService.hasJustClosedDropdownWithEscape()) {
return; // Do not close the dialog with the same Escape key press
}
if (this._dialogService.dialogsOpen().length === this._orderNumber) {
this._dialogService.close();
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogComponent, deps: [{ token: FudisTranslationService }, { token: FudisDialogService }, { token: FudisIdService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: DialogComponent, isStandalone: true, selector: "fudis-dialog", inputs: { size: "size" }, host: { listeners: { "window:keyup": "_handleEscapePress($event)" } }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"fudis-dialog fudis-dialog__size__{{ size }}\"\n [class.fudis-dialog--scrollable]=\"_dialogScrollable()\"\n [attr.id]=\"_id\"\n>\n <fudis-icon-button\n fudisDialogClose\n class=\"fudis-dialog__close\"\n [class.fudis-dialog__close__absolute]=\"closeButtonPositionAbsolute()\"\n [attr.id]=\"_id + '-close'\"\n [ariaLabel]=\"_translateService.getTranslations()().DIALOG.CLOSE\"\n [size]=\"'extra-small'\"\n [icon]=\"'ring-close-fill'\"\n [variant]=\"'tertiary'\"\n >\n </fudis-icon-button>\n <ng-content></ng-content>\n</div>\n<fudis-alert-group [insideDialog]=\"true\" />\n", styles: [".fudis-dialog{border:1px solid #484848;box-sizing:border-box;position:relative;padding:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));max-width:calc(100vw - 2rem / var(--fudis-rem-multiplier))}.fudis-dialog__close{float:right}.fudis-dialog__close__absolute{position:absolute;right:calc(1rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-dialog__close__absolute{right:calc(2.5rem / var(--fudis-rem-multiplier))}}.fudis-dialog__size__xs{width:calc(30rem / var(--fudis-rem-multiplier))}.fudis-dialog__size__sm{width:calc(44rem / var(--fudis-rem-multiplier))}.fudis-dialog__size__md{width:calc(55rem / var(--fudis-rem-multiplier))}.fudis-dialog__size__lg{width:calc(70rem / var(--fudis-rem-multiplier))}.fudis-dialog__size__xl{width:calc(90rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-dialog{padding:calc(1.5rem / var(--fudis-rem-multiplier)) calc(2.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-dialog{max-width:calc(100vw - 4rem / var(--fudis-rem-multiplier))}}\n"], dependencies: [{ kind: "component", type: IconButtonComponent, selector: "fudis-icon-button", inputs: ["ariaLabel", "icon", "asMenuButton"] }, { kind: "directive", type: DialogCloseDirective, selector: "[fudisDialogClose]" }, { kind: "component", type: AlertGroupComponent, selector: "fudis-alert-group", inputs: ["position", "insideDialog"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DialogComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-dialog', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconButtonComponent, DialogCloseDirective, AlertGroupComponent], template: "<div\n class=\"fudis-dialog fudis-dialog__size__{{ size }}\"\n [class.fudis-dialog--scrollable]=\"_dialogScrollable()\"\n [attr.id]=\"_id\"\n>\n <fudis-icon-button\n fudisDialogClose\n class=\"fudis-dialog__close\"\n [class.fudis-dialog__close__absolute]=\"closeButtonPositionAbsolute()\"\n [attr.id]=\"_id + '-close'\"\n [ariaLabel]=\"_translateService.getTranslations()().DIALOG.CLOSE\"\n [size]=\"'extra-small'\"\n [icon]=\"'ring-close-fill'\"\n [variant]=\"'tertiary'\"\n >\n </fudis-icon-button>\n <ng-content></ng-content>\n</div>\n<fudis-alert-group [insideDialog]=\"true\" />\n", styles: [".fudis-dialog{border:1px solid #484848;box-sizing:border-box;position:relative;padding:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));max-width:calc(100vw - 2rem / var(--fudis-rem-multiplier))}.fudis-dialog__close{float:right}.fudis-dialog__close__absolute{position:absolute;right:calc(1rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-dialog__close__absolute{right:calc(2.5rem / var(--fudis-rem-multiplier))}}.fudis-dialog__size__xs{width:calc(30rem / var(--fudis-rem-multiplier))}.fudis-dialog__size__sm{width:calc(44rem / var(--fudis-rem-multiplier))}.fudis-dialog__size__md{width:calc(55rem / var(--fudis-rem-multiplier))}.fudis-dialog__size__lg{width:calc(70rem / var(--fudis-rem-multiplier))}.fudis-dialog__size__xl{width:calc(90rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-dialog{padding:calc(1.5rem / var(--fudis-rem-multiplier)) calc(2.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-dialog{max-width:calc(100vw - 4rem / var(--fudis-rem-multiplier))}}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: FudisDialogService }, { type: FudisIdService }, { type: i0.ElementRef }], propDecorators: { size: [{
type: Input
}], _handleEscapePress: [{
type: HostListener,
args: ['window:keyup', ['$event']]
}] } });
class DropdownBaseDirective {
constructor() {
/**
* Binding host CSS class to component wrapper
*/
this.hostClass = 'fudis-dropdown-menu-host';
/**
* Output emitter for focus event
*/
this.handleFocus = new EventEmitter();
/**
* Output emitter for blur event
*/
this.handleBlur = new EventEmitter();
}
/**
* When dropdown is focused, happens e. g. with Firefox
*/
_dropdownFocus(event) {
this.handleFocus.emit(event);
}
/**
* When dropdown is blurred, happens e. g. with Firefox
*/
_dropdownBlur(event) {
this.handleBlur.emit(event);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownBaseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: DropdownBaseDirective, isStandalone: true, selector: "[fudisDropdownBase]", outputs: { handleFocus: "handleFocus", handleBlur: "handleBlur" }, host: { properties: { "class": "this.hostClass" } }, viewQueries: [{ propertyName: "dropdownElement", first: true, predicate: ["dropdownElement"], descendants: true }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownBaseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisDropdownBase]' }]
}], propDecorators: { dropdownElement: [{
type: ViewChild,
args: ['dropdownElement']
}], hostClass: [{
type: HostBinding,
args: ['class']
}], handleFocus: [{
type: Output
}], handleBlur: [{
type: Output
}] } });
/**
* Displays a list of actions in a collapsible menu. Must be a direct child of `fudis-icon-button`
* with `[asMenuButton]="true"`.
*
* Use this component to group secondary or contextual actions.
*
* @example
* ```html
* <fudis-icon-button [ariaLabel]="'Actions'" [size]="'small'" [variant]="'secondary'" [icon]="'three-dots'" [asMenuButton]="true">
* <fudis-dropdown-menu>
* <fudis-dropdown-menu-item [label]="'Edit'" (handleClick)="edit($event)"></fudis-dropdown-menu-item>
* </fudis-dropdown-menu>
* </fudis-icon-button>
* ```;
*/
class DropdownMenuComponent extends DropdownBaseDirective {
constructor(_idService, _dropdownEventService, _dialogService, _document, _parentButton) {
super();
this._idService = _idService;
this._dropdownEventService = _dropdownEventService;
this._dialogService = _dialogService;
this._document = _document;
this._parentButton = _parentButton;
/**
* Align Dropdown Menu opening position: 'left' | 'center' | 'right'
*/
this.align = 'center';
/**
* Dropdown Menu size: 'sm' | 'md' | 'lg'
*/
this.size = 'lg';
/**
* Determine dropdown max-width
*/
this._maxWidth = signal('initial', ...(ngDevMode ? [{ debugName: "_maxWidth" }] : /* istanbul ignore next */ []));
/**
* Currently focused option
*/
this._focusedOption = null;
/**
* Generate id in constructor for it to be accessible to child elements, and set it to parent
* button's dropdownMenuId property for aria-controls
*/
this.id = this._idService.getNewGrandParentId('dropdown-menu');
if (this._parentButton)
this._parentButton.dropdownMenuId = this.id;
/**
* Fire maxWidth calculation through Observable call from parent Button
*/
this._subscription = this._dropdownEventService.triggerCalculation.subscribe(() => {
this._getMaxWidth();
});
}
/**
* Add or remove currently focused option. Called from DropdownMenuItem.
*/
setFocusedOption(id, type, event) {
if (type === 'add') {
this._focusedOption = id;
}
else if (event) {
this._focusedOption = null;
this._componentFocused(event).then((value) => {
if (!value) {
this._parentButton.closeMenu(false);
}
});
}
}
/**
* Check if focus is inside the Dropdown Menu component
*
* @param event Focus event
* @returns Boolean
*/
_componentFocused(event) {
return new Promise((resolve) => {
let counter = 0;
const nextTarget = event?.relatedTarget;
const focusCheckInterval = setInterval(() => {
const focused = !!this._dropdownMenuElement.nativeElement.contains(this._document.activeElement) ||
!!this._dropdownMenuElement.nativeElement.contains(nextTarget);
// If focus has moved to another element inside Dropdown Menu
if (focused) {
clearInterval(focusCheckInterval);
resolve(true);
// If focus target is null
}
else if (!nextTarget) {
clearInterval(focusCheckInterval);
resolve(false);
// Increase counter, and try again. This is needed usually with click events as between previous element blur and next element focus click event is "somewhere else"
}
else if (counter <= 100) {
counter = counter + 50;
}
else {
// Else resolve boolean check after two tries, if any relevant element is focused
clearInterval(focusCheckInterval);
resolve(!!this._focusedOption);
}
}, 50);
});
}
closeDropdownMenu() {
this._parentButton.closeMenu();
}
/**
* Host Listener for dropdown's width, it needs to be wider than its Button parent
*/
_getMaxWidth() {
const fontSize = Number(window.getComputedStyle(this._document.body).getPropertyValue('font-size').replace('px', ''));
const elementInViewWidth = this._dropdownMenuElement?.nativeElement?.getBoundingClientRect()?.width;
// Left boundary edge of the element in the viewport
const elementInViewX = this._dropdownMenuElement?.nativeElement?.getBoundingClientRect()?.x;
if (elementInViewX && elementInViewWidth && elementInViewWidth !== 0 && this.align === 'left') {
const viewWidthAndViewXInRem = `${(elementInViewWidth + elementInViewX) / fontSize}rem`;
this._maxWidth.set('calc(' + viewWidthAndViewXInRem + '/ var(--fudis-rem-multiplier))');
}
else if (window?.visualViewport?.width && elementInViewX) {
// elementInViewX + 1 is a hack for Firefox. In desktop mode (but in small screen) the dropdown menu's right border did not show.
// TODO: This could be improved if better solution comes to mind.
const viewportWidthAndViewXInRem = `${(window.visualViewport.width - (elementInViewX + 1)) / fontSize}rem`;
this._maxWidth.set('calc(' + viewportWidthAndViewXInRem + '/ var(--fudis-rem-multiplier))');
}
else {
this._maxWidth.set('initial');
}
}
/**
* Host Listener for keydown events, especially Arrow Down and Escape from Dropdown Menu
*/
_handleDropdownMenuKeyDown(event) {
if (this._parentButton.dropdownOpen.value) {
if (event.key === 'ArrowDown') {
event.preventDefault();
const firstChildElement = this._dropdownMenuElement.nativeElement.children[0];
// If focus is on the menu button, only then listen keydown and focus on the first child
if (firstChildElement.closest('fudis-icon-button')?.querySelector('.fudis-button') ===
document.activeElement) {
const firstChildButtonElement = firstChildElement.querySelector('button');
firstChildButtonElement?.focus();
}
}
if (event.key === 'Escape') {
event.preventDefault();
event.stopPropagation();
this._dialogService.dropdownClosedWithEscape();
this._parentButton.closeMenu();
}
}
}
ngOnDestroy() {
this._subscription.unsubscribe();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownMenuComponent, deps: [{ token: FudisIdService }, { token: DropdownEventService }, { token: FudisDialogService }, { token: DOCUMENT }, { token: IconButtonComponent, host: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: DropdownMenuComponent, isStandalone: true, selector: "fudis-dropdown-menu", inputs: { align: "align", size: "size" }, host: { listeners: { "window:click": "_getMaxWidth()", "window:keydown": "_handleDropdownMenuKeyDown($event)" } }, viewQueries: [{ propertyName: "_dropdownMenuElement", first: true, predicate: ["dropdownMenuElement"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<div\n #dropdownMenuElement\n class=\"fudis-dropdown-menu fudis-dropdown-menu__{{ align }} fudis-dropdown-menu__{{ size }}\"\n [attr.id]=\"id\"\n [attr.role]=\"'menu'\"\n [attr.aria-expanded]=\"true\"\n [style.max-width]=\"_maxWidth()\"\n>\n <ng-content select=\"fudis-dropdown-menu-item\"></ng-content>\n <ng-content select=\"fudis-dropdown-menu-group\"></ng-content>\n</div>\n", styles: [".fudis-dropdown-menu{box-sizing:border-box;margin:0;padding:0;border:1px solid #484848;background-color:#fff;z-index:100;position:absolute}.fudis-dropdown-menu:focus-visible{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-dropdown-menu__right{left:0}.fudis-dropdown-menu__left{right:0}.fudis-dropdown-menu__center{right:initial;left:initial}.fudis-dropdown-menu__lg{width:calc(23rem / var(--fudis-rem-multiplier))}.fudis-dropdown-menu__md{width:calc(14rem / var(--fudis-rem-multiplier))}.fudis-dropdown-menu__sm{width:calc(10rem / var(--fudis-rem-multiplier))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownMenuComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-dropdown-menu', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #dropdownMenuElement\n class=\"fudis-dropdown-menu fudis-dropdown-menu__{{ align }} fudis-dropdown-menu__{{ size }}\"\n [attr.id]=\"id\"\n [attr.role]=\"'menu'\"\n [attr.aria-expanded]=\"true\"\n [style.max-width]=\"_maxWidth()\"\n>\n <ng-content select=\"fudis-dropdown-menu-item\"></ng-content>\n <ng-content select=\"fudis-dropdown-menu-group\"></ng-content>\n</div>\n", styles: [".fudis-dropdown-menu{box-sizing:border-box;margin:0;padding:0;border:1px solid #484848;background-color:#fff;z-index:100;position:absolute}.fudis-dropdown-menu:focus-visible{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-dropdown-menu__right{left:0}.fudis-dropdown-menu__left{right:0}.fudis-dropdown-menu__center{right:initial;left:initial}.fudis-dropdown-menu__lg{width:calc(23rem / var(--fudis-rem-multiplier))}.fudis-dropdown-menu__md{width:calc(14rem / var(--fudis-rem-multiplier))}.fudis-dropdown-menu__sm{width:calc(10rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: DropdownEventService }, { type: FudisDialogService }, { type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: IconButtonComponent, decorators: [{
type: Host
}] }], propDecorators: { _dropdownMenuElement: [{
type: ViewChild,
args: ['dropdownMenuElement']
}], align: [{
type: Input
}], size: [{
type: Input
}], _getMaxWidth: [{
type: HostListener,
args: ['window:click']
}], _handleDropdownMenuKeyDown: [{
type: HostListener,
args: ['window:keydown', ['$event']]
}] } });
class DropdownItemBaseDirective {
constructor(_document) {
this._document = _document;
/**
* Option for closing or leaving dropdown open after clicking an item. Closes by default.
*/
this.close = true;
/**
* Checked state for multiselect checkbox
*/
this.checked = false;
/**
* Ouput for click event
*/
this.handleClick = new EventEmitter();
/**
* Output for checked state in multiselect
*/
this.handleChecked = new EventEmitter();
/**
* Output for blur event in select option
*/
this.handleBlur = new EventEmitter();
}
/**
* Trigger focus based on keyboard interaction. Used to focus on next / previous dropdown item
* element
*/
_baseHandleKeyDown(event, element, cssClassSelector) {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
const focusElement = this._document.querySelector(':focus');
const siblingElements = element.nativeElement
.closest('.fudis-select-dropdown, .fudis-dropdown-menu')
.querySelectorAll(cssClassSelector);
const siblingElementsCount = siblingElements.length - 1;
event.preventDefault();
const focusIndex = Array.prototype.indexOf.call(siblingElements, focusElement);
let elementToFocus = element.nativeElement;
if (event.key === 'ArrowUp') {
elementToFocus = siblingElements[focusIndex > 0 ? focusIndex - 1 : siblingElementsCount];
}
if (event.key === 'ArrowDown') {
elementToFocus = siblingElements[focusIndex < siblingElementsCount ? focusIndex + 1 : 0];
}
elementToFocus.focus();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownItemBaseDirective, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: DropdownItemBaseDirective, isStandalone: true, selector: "[fudisDropdownItemBase]", inputs: { close: "close", checked: "checked" }, outputs: { handleClick: "handleClick", handleChecked: "handleChecked", handleBlur: "handleBlur" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownItemBaseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisDropdownItemBase]' }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }], propDecorators: { close: [{
type: Input
}], checked: [{
type: Input
}], handleClick: [{
type: Output
}], handleChecked: [{
type: Output
}], handleBlur: [{
type: Output
}] } });
/**
* Groups related dropdown items.
*
* Use this component to organize related menu options to improve readability.
*/
class DropdownMenuGroupComponent {
constructor(_idService, _parentDropdownMenu) {
this._idService = _idService;
this._parentDropdownMenu = _parentDropdownMenu;
if (_parentDropdownMenu) {
this.id = this._idService.getNewGroupId('dropdown-menu', this._parentDropdownMenu.id);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownMenuGroupComponent, deps: [{ token: FudisIdService }, { token: DropdownMenuComponent, host: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: DropdownMenuGroupComponent, isStandalone: true, selector: "fudis-dropdown-menu-group", inputs: { label: "label" }, ngImport: i0, template: "<div class=\"fudis-dropdown-menu-group\" [attr.aria-labelledby]=\"id\" role=\"group\">\n <span class=\"fudis-dropdown-menu-group__label\" [attr.aria-hidden]=\"true\" [attr.id]=\"id\">{{\n label\n }}</span>\n <ng-content></ng-content>\n</div>\n", styles: [".fudis-dropdown-menu-group__label{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:300;color:#727272;box-sizing:border-box;display:flex;align-items:center;border-top:1px solid #d4d4d4;padding-left:calc(.5rem / var(--fudis-rem-multiplier));min-height:calc(2rem / var(--fudis-rem-multiplier))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownMenuGroupComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-dropdown-menu-group', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fudis-dropdown-menu-group\" [attr.aria-labelledby]=\"id\" role=\"group\">\n <span class=\"fudis-dropdown-menu-group__label\" [attr.aria-hidden]=\"true\" [attr.id]=\"id\">{{\n label\n }}</span>\n <ng-content></ng-content>\n</div>\n", styles: [".fudis-dropdown-menu-group__label{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:300;color:#727272;box-sizing:border-box;display:flex;align-items:center;border-top:1px solid #d4d4d4;padding-left:calc(.5rem / var(--fudis-rem-multiplier));min-height:calc(2rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: DropdownMenuComponent, decorators: [{
type: Host
}] }], propDecorators: { label: [{
type: Input,
args: [{ required: true }]
}] } });
/**
* Single menu item for DropdownMenuComponent.
*/
class DropdownMenuItemComponent extends DropdownItemBaseDirective {
constructor(_idService, _translationService, _document, _parentDropdownMenu, _parentGroup) {
super(_document);
this._idService = _idService;
this._translationService = _translationService;
this._parentDropdownMenu = _parentDropdownMenu;
this._parentGroup = _parentGroup;
/**
* If Menu Item is disabled
*/
this.disabled = false;
/**
* Internal translated text for disabled dropdown menu item
*/
this._translationItemDisabledText = new BehaviorSubject('');
this._id = this._idService.getNewDropdownMenuId(this._parentDropdownMenu.id, this._parentGroup?.id);
effect(() => {
const translations = _translationService.getTranslations()();
this._translationItemDisabledText.next(translations.DROPDOWNMENU.ITEM.DISABLED);
});
}
/**
* Handle key down event
*/
_handleKeyDown(event) {
if (event.key === 'Enter' || event.key === ' ') {
if (this.disabled) {
event.preventDefault();
return;
}
else {
event.preventDefault();
this._clickOption(event);
}
}
else if (event.key !== ' ') {
this._baseHandleKeyDown(event, this.dropdownItem, '.fudis-dropdown-menu-item__focusable');
}
}
/**
* Handle blur event
*/
_handleMenuItemBlur(event) {
this._parentDropdownMenu.setFocusedOption(this._id, 'remove', event);
}
/**
* Handle focus
*/
_handleMenuItemFocus() {
this._parentDropdownMenu.setFocusedOption(this._id, 'add');
}
/**
* Click handler for Dropdown Menu Item click
*/
_clickOption(event) {
if (!this.disabled) {
this.handleClick.emit(event);
this._parentDropdownMenu.closeDropdownMenu();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownMenuItemComponent, deps: [{ token: FudisIdService }, { token: FudisTranslationService }, { token: DOCUMENT }, { token: DropdownMenuComponent, host: true }, { token: DropdownMenuGroupComponent, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DropdownMenuItemComponent, isStandalone: true, selector: "fudis-dropdown-menu-item", inputs: { label: "label", disabled: "disabled" }, viewQueries: [{ propertyName: "dropdownItem", first: true, predicate: ["dropdownItem"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<button\n #dropdownItem\n class=\"fudis-dropdown-menu-item\"\n [class.fudis-dropdown-menu-item--disabled]=\"disabled || null\"\n [class.fudis-dropdown-menu-item__focusable]=\"!disabled\"\n [attr.id]=\"_id\"\n [attr.role]=\"'menuitem'\"\n [attr.aria-disabled]=\"disabled || null\"\n (keydown)=\"_handleKeyDown($event)\"\n (blur)=\"_handleMenuItemBlur($event)\"\n (focus)=\"_handleMenuItemFocus()\"\n (click)=\"_clickOption($event)\"\n tabindex=\"-1\"\n>\n {{ label }}\n @if (disabled || null) {\n <span class=\"fudis-dropdown-menu-item__label fudis-dropdown-menu-item__label__disabled-text\">\n ({{ _translationItemDisabledText | async }})</span\n >\n }\n</button>\n", styles: [".fudis-dropdown-menu-item{box-sizing:border-box;margin:0;padding:0;text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:300;background-color:transparent;border:none;display:flex;position:relative;align-items:flex-start;justify-content:space-between;padding:calc(.5rem / var(--fudis-rem-multiplier));width:100%;overflow:hidden;text-align:left;cursor:pointer}.fudis-dropdown-menu-item:hover{background-color:var(--fudis-color-primary-light)}.fudis-dropdown-menu-item:focus-visible,.fudis-dropdown-menu-item:focus{outline:2px dashed #484848;outline-offset:-2px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-dropdown-menu-item:active:not(.fudis-dropdown-menu-item--disabled){background-color:var(--fudis-color-primary);color:#fff}.fudis-dropdown-menu-item--disabled{background-color:#f1f1f1;color:#727272;cursor:default}.fudis-dropdown-menu-item__label{line-height:calc(1rem / var(--fudis-rem-multiplier))}.fudis-dropdown-menu-item__label__disabled-text{padding-right:calc(.5rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DropdownMenuItemComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-dropdown-menu-item', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AsyncPipe], template: "<button\n #dropdownItem\n class=\"fudis-dropdown-menu-item\"\n [class.fudis-dropdown-menu-item--disabled]=\"disabled || null\"\n [class.fudis-dropdown-menu-item__focusable]=\"!disabled\"\n [attr.id]=\"_id\"\n [attr.role]=\"'menuitem'\"\n [attr.aria-disabled]=\"disabled || null\"\n (keydown)=\"_handleKeyDown($event)\"\n (blur)=\"_handleMenuItemBlur($event)\"\n (focus)=\"_handleMenuItemFocus()\"\n (click)=\"_clickOption($event)\"\n tabindex=\"-1\"\n>\n {{ label }}\n @if (disabled || null) {\n <span class=\"fudis-dropdown-menu-item__label fudis-dropdown-menu-item__label__disabled-text\">\n ({{ _translationItemDisabledText | async }})</span\n >\n }\n</button>\n", styles: [".fudis-dropdown-menu-item{box-sizing:border-box;margin:0;padding:0;text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:300;background-color:transparent;border:none;display:flex;position:relative;align-items:flex-start;justify-content:space-between;padding:calc(.5rem / var(--fudis-rem-multiplier));width:100%;overflow:hidden;text-align:left;cursor:pointer}.fudis-dropdown-menu-item:hover{background-color:var(--fudis-color-primary-light)}.fudis-dropdown-menu-item:focus-visible,.fudis-dropdown-menu-item:focus{outline:2px dashed #484848;outline-offset:-2px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-dropdown-menu-item:active:not(.fudis-dropdown-menu-item--disabled){background-color:var(--fudis-color-primary);color:#fff}.fudis-dropdown-menu-item--disabled{background-color:#f1f1f1;color:#727272;cursor:default}.fudis-dropdown-menu-item__label{line-height:calc(1rem / var(--fudis-rem-multiplier))}.fudis-dropdown-menu-item__label__disabled-text{padding-right:calc(.5rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: FudisTranslationService }, { type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: DropdownMenuComponent, decorators: [{
type: Host
}] }, { type: DropdownMenuGroupComponent, decorators: [{
type: Host
}, {
type: Optional
}] }], propDecorators: { dropdownItem: [{
type: ViewChild,
args: ['dropdownItem']
}], label: [{
type: Input,
args: [{ required: true }]
}], disabled: [{
type: Input
}] } });
/**
* Displays an information message.
*
* Use this component to inform users about events, updates, or system feedback.
*/
class NotificationComponent {
constructor(_translateService) {
this._translateService = _translateService;
/**
* Notification variant: 'warning' | 'danger' | 'success' | 'info'
*/
this.variant = 'warning';
}
focus() {
if (this.articleElement?.nativeElement) {
this.articleElement.nativeElement.focus();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NotificationComponent, deps: [{ token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: NotificationComponent, isStandalone: true, selector: "fudis-notification", inputs: { variant: "variant", ariaDescribedby: "ariaDescribedby" }, viewQueries: [{ propertyName: "articleElement", first: true, predicate: ["articleElement"], descendants: true }], ngImport: i0, template: "<article\n #articleElement\n tabindex=\"-1\"\n [attr.aria-describedby]=\"ariaDescribedby\"\n class=\"fudis-notification fudis-notification__{{ variant }}\"\n>\n <fudis-body-text class=\"fudis-visually-hidden\">{{\n _translateService.getTranslations()().ATTENTION\n }}</fudis-body-text>\n @if (variant === \"warning\") {\n <fudis-icon [icon]=\"'exclamation-mark-circle'\" />\n }\n @if (variant === \"danger\") {\n <fudis-icon [icon]=\"'alert'\" />\n }\n @if (variant === \"success\") {\n <fudis-icon [icon]=\"'checkmark-circle'\" />\n }\n @if (variant === \"info\") {\n <fudis-icon [icon]=\"'info-circle'\" />\n }\n <div class=\"fudis-notification__content\">\n <ng-content />\n </div>\n</article>\n", styles: [".fudis-notification:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}\n"], dependencies: [{ kind: "component", type: BodyTextComponent, selector: "fudis-body-text", inputs: ["variant", "align", "lang"] }, { kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NotificationComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-notification', changeDetection: ChangeDetectionStrategy.OnPush, imports: [BodyTextComponent, IconComponent], template: "<article\n #articleElement\n tabindex=\"-1\"\n [attr.aria-describedby]=\"ariaDescribedby\"\n class=\"fudis-notification fudis-notification__{{ variant }}\"\n>\n <fudis-body-text class=\"fudis-visually-hidden\">{{\n _translateService.getTranslations()().ATTENTION\n }}</fudis-body-text>\n @if (variant === \"warning\") {\n <fudis-icon [icon]=\"'exclamation-mark-circle'\" />\n }\n @if (variant === \"danger\") {\n <fudis-icon [icon]=\"'alert'\" />\n }\n @if (variant === \"success\") {\n <fudis-icon [icon]=\"'checkmark-circle'\" />\n }\n @if (variant === \"info\") {\n <fudis-icon [icon]=\"'info-circle'\" />\n }\n <div class=\"fudis-notification__content\">\n <ng-content />\n </div>\n</article>\n", styles: [".fudis-notification:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }], propDecorators: { articleElement: [{
type: ViewChild,
args: ['articleElement']
}], variant: [{
type: Input
}], ariaDescribedby: [{
type: Input
}] } });
/**
* Adds link behavior to an element.
*
* Use this directive with anchor element and consider whether link is internal/external.
*/
class LinkDirective {
constructor(_document, _bindedElement, _focusService, _idService, _translationService) {
this._document = _document;
this._bindedElement = _bindedElement;
this._focusService = _focusService;
this._idService = _idService;
this._translationService = _translationService;
/**
* Link default size is 'md' (14px) which is the default size of <fudis-body-text> component. If
* link is used inside of Body Text it will always inherit its parent's font-size.
*/
this.size = 'md';
/**
* Set browser focus to link on the first load.
*/
this.initialFocus = false;
/**
* If link opens in a new tab
*/
this.external = false;
/**
* Helper class for link size
*/
this._sizeCssClass = `fudis-link__size__md`;
/**
* Container ref to attach Icon Component
*/
this._viewContainerRef = inject(ViewContainerRef);
/**
* Helper array for parsing long link texts with new-tab icon
*/
this._parsedTitle = [];
/**
* Aria-label for the external link
*/
this._externalLinkAriaLabel = new BehaviorSubject(this._translationService.getTranslations()().LINK.EXTERNAL_LINK);
effect(() => {
this._externalLinkAriaLabel.next(this._translationService.getTranslations()().LINK.EXTERNAL_LINK);
});
}
ngAfterViewInit() {
if (this.initialFocus && !this._focusService.isIgnored(this.id)) {
this._bindedElement.nativeElement.focus();
}
}
ngOnInit() {
if (this.id) {
this._idService.addNewId('link', this.id);
}
else {
this.id = this._idService.getNewId('link');
}
this._setSizeClass();
this._bindedElement.nativeElement.classList.add('fudis-link');
this._bindedElement.nativeElement.setAttribute('id', this.id);
}
ngOnChanges(changes) {
if (changes.size?.previousValue !== changes.size?.currentValue) {
this._setSizeClass();
}
const titleChanged = changes.title?.currentValue !== changes.title?.previousValue;
const externalChanged = changes.external?.currentValue !== changes.external?.previousValue;
if (titleChanged || (externalChanged && changes.title?.currentValue)) {
this._setHtmlAttributes();
if (titleChanged) {
this._parseTitle();
if (!changes.title?.isFirstChange() && this.external) {
this._updateTitleHtml();
}
}
this._setExternalHtml();
}
}
_updateTitleHtml() {
// Remove texts from parent
this._bindedElement.nativeElement.childNodes.forEach((node) => node.nodeType === Node.TEXT_NODE && node.remove());
const iconWrapper = this._bindedElement.nativeElement.querySelector('.fudis-link__external__icon-wrapper');
if (iconWrapper) {
// Remove texts from icon wrapper
iconWrapper.childNodes.forEach((node) => node.nodeType === Node.TEXT_NODE && node.remove());
if (this._parsedTitle.length === 1) {
const text = this._document.createTextNode(this._parsedTitle[0]);
iconWrapper.prepend(text);
}
else {
const textStart = this._document.createTextNode(`${this._parsedTitle[0]} `);
const textEnd = this._document.createTextNode(this._parsedTitle[1]);
this._bindedElement.nativeElement.prepend(textStart);
iconWrapper.prepend(textEnd);
}
}
}
/**
* Set size CSS class
*/
_setSizeClass() {
const currentSize = this._sizeCssClass;
this._bindedElement.nativeElement.classList.remove(currentSize);
this._sizeCssClass = `fudis-link__size__${this.size}`;
this._bindedElement.nativeElement.classList.add(this._sizeCssClass);
}
/**
* Add to remove relevant HTML attributes from binded element.
*/
_setHtmlAttributes() {
if (this.external) {
this._bindedElement.nativeElement.setAttribute('target', '_blank');
this._bindedElement.nativeElement.setAttribute('rel', 'noopener noreferrer');
this._bindedElement.nativeElement.setAttribute('aria-label', `${this.title}, ${this._externalLinkAriaLabel.value}`);
}
else {
this._bindedElement.nativeElement.removeAttribute('target');
this._bindedElement.nativeElement.removeAttribute('rel');
this._bindedElement.nativeElement.setAttribute('aria-label', this.title);
const titleNode = this._document.createTextNode(this.title);
this._bindedElement.nativeElement.replaceChildren(titleNode);
}
}
_setExternalHtml() {
if (this.external) {
// Create HTML semantics
const html = String.raw;
if (this._parsedTitle.length === 1) {
this._bindedElement.nativeElement.innerHTML = html `<span
class="fudis-link__external__icon-wrapper"
>${this._parsedTitle[0]}<span class="fudis-link__external__icon"></span>
</span>`;
}
else {
this._bindedElement.nativeElement.innerHTML = html `${this._parsedTitle[0]}
<span class="fudis-link__external__icon-wrapper"
>${this._parsedTitle[1]}<span class="fudis-link__external__icon"></span>
</span>`;
}
// Create Icon Component and define properties
this._iconComponentRef = this._viewContainerRef.createComponent(IconComponent);
this._iconComponentRef.setInput('icon', 'new-tab');
this._iconComponentRef.setInput('color', 'primary-dark');
// Append Icon Component to right place in the HTML when DOM is updated
const component = this._iconComponentRef.instance.elementRef.nativeElement;
const observer = new MutationObserver(() => {
const iconLocation = this._bindedElement.nativeElement.querySelector('.fudis-link__external__icon');
if (iconLocation) {
iconLocation.append(component);
observer.disconnect();
}
});
observer.observe(this._document, {
attributes: false,
childList: true,
characterData: false,
subtree: true,
});
}
else {
// If not External, detach Icon Component instance
this._viewContainerRef.detach();
}
}
/**
* Used with external links to split the last word of the title to be paired with the Icon, so
* that on line break, the icon sticks with the last word of the title.
*/
_parseTitle() {
const toArray = this.title.split(' ').filter((n) => n);
if (toArray.length > 1) {
const lastWord = toArray[toArray.length - 1];
const titleStart = toArray.slice(0, -1).join(' ');
this._parsedTitle = [titleStart, lastWord];
}
else {
this._parsedTitle = [toArray[0]];
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LinkDirective, deps: [{ token: DOCUMENT }, { token: i0.ElementRef }, { token: FudisFocusService }, { token: FudisIdService }, { token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: LinkDirective, isStandalone: true, selector: "[fudisLink]", inputs: { size: "size", initialFocus: "initialFocus", id: "id", external: "external", title: "title" }, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LinkDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisLink]' }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: i0.ElementRef }, { type: FudisFocusService }, { type: FudisIdService }, { type: FudisTranslationService }], propDecorators: { size: [{
type: Input
}], initialFocus: [{
type: Input
}], id: [{
type: Input
}], external: [{
type: Input
}], title: [{
type: Input,
args: [{ required: true }]
}] } });
/**
* Displays a summary of validation errors.
*
* Use this component to present form errors in a single, accessible location.
*/
class ErrorSummaryComponent {
constructor(_errorSummaryService, _translationService) {
this._errorSummaryService = _errorSummaryService;
this._translationService = _translationService;
this.handleUpdatedErrorList = new EventEmitter();
/**
* Visible errors
*/
this._visibleErrorList = signal([], ...(ngDevMode ? [{ debugName: "_visibleErrorList" }] : /* istanbul ignore next */ []));
/**
* Focus counter to hit the correct focus field
*/
this._numberOfFocusTries = 0;
this._injector = inject(Injector);
toObservable(this._errorSummaryService.submitAttempt, { injector: this._injector }).subscribe(() => {
this._focusToErrorSummary();
});
}
/**
* To enable clicking Error Summary links and then moving focus to corresponding form field. This
* was needed, as not all Fudis applications use Angular Router, so alternative approach was
* needed.
*
* @param event Original click event
* @param clickedId Id of clicked link in Error Summary
*/
handleErrorClick(event, clickedId) {
event.preventDefault();
const elementToFocus = this.parentComponent.querySelector(`#${clickedId}`);
if (elementToFocus) {
elementToFocus.focus();
}
}
/**
* Sort errors the same order they appear in the DOM
*/
_sortErrorOrder(a, b) {
if (a.id === b.id) {
return 0;
}
if (a.element && b.element) {
const position = a.element.compareDocumentPosition(b.element);
if (position & Node.DOCUMENT_POSITION_FOLLOWING ||
position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
return -1;
}
else if (position & Node.DOCUMENT_POSITION_PRECEDING ||
position & Node.DOCUMENT_POSITION_CONTAINS) {
return 1;
}
}
return 0;
}
/**
* Update Error Summary content with possible parent Fieldsets, Sections and Expandables
* (Sections)
*/
_updateSummaryContent(content) {
const newErrorList = [];
const fieldsets = this._errorSummaryService.formStructure[this.formId].fieldsets;
const sections = this._errorSummaryService.formStructure[this.formId].sections;
Object.keys(content).forEach((errorId) => {
Object.values(content[errorId]).forEach((error) => {
const parentFieldset = Object.keys(fieldsets).find((fieldset) => {
if (this.parentComponent?.querySelector(`#${fieldset} #${errorId}`)) {
return fieldset;
}
return null;
});
const parentSection = Object.keys(sections).find((section) => {
if (this.parentComponent?.querySelector(`#${section} #${errorId}`)) {
return section;
}
return null;
});
const parentSectionString = parentSection ? `${sections[parentSection]} / ` : '';
const parentFieldsetString = parentFieldset ? `${fieldsets[parentFieldset]} / ` : '';
const cleanedError = error.replace(/[:!?]$/, '');
const newItem = {
id: errorId,
message: `${parentSectionString}${parentFieldsetString}${cleanedError}`,
element: this.parentComponent.querySelector(`#${errorId}`),
};
newErrorList.push(newItem);
});
});
const sortedList = newErrorList.sort(this._sortErrorOrder);
this._visibleErrorList.set(sortedList);
if (sortedList.length > 0) {
const newOutputList = [];
sortedList.forEach((item) => {
newOutputList.push({ id: item.id, message: item.message });
});
this.handleUpdatedErrorList.emit(newOutputList);
}
else {
this.handleUpdatedErrorList.emit(null);
}
if (this._errorSummaryService.focusToFormOnReload === this.formId) {
this._focusToErrorSummary();
}
}
/**
* Move focus to Error Summary if errors are visible
*/
_focusToErrorSummary() {
if (this._focusTarget?.articleElement && this._visibleErrorList().length > 0) {
this._numberOfFocusTries = 0;
this._focusTarget.focus();
}
else if (this._numberOfFocusTries < 20) {
setTimeout(() => {
this._numberOfFocusTries += 1;
this._focusToErrorSummary();
}, 100);
}
}
ngOnInit() {
/**
* Fetch and update current visible errors when reloadFormErrors() is called
*/
toObservable(this._errorSummaryService.errorsSignal[this.formId], {
injector: this._injector,
}).subscribe((value) => {
this._updateSummaryContent(value);
});
}
ngAfterViewInit() {
this._focusToErrorSummary();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ErrorSummaryComponent, deps: [{ token: FudisInternalErrorSummaryService }, { token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ErrorSummaryComponent, isStandalone: true, selector: "fudis-error-summary", inputs: { parentComponent: "parentComponent", title: "title", formId: "formId" }, outputs: { handleUpdatedErrorList: "handleUpdatedErrorList" }, viewQueries: [{ propertyName: "_focusTarget", first: true, predicate: ["focusTarget"], descendants: true }], ngImport: i0, template: "@if (_visibleErrorList(); as errors) {\n @if (errors.length > 0) {\n <fudis-notification\n class=\"fudis-error-summary\"\n [variant]=\"'danger'\"\n [ariaDescribedby]=\"'fudis-error-summary-title-id'\"\n #focusTarget\n >\n <h2 id=\"fudis-error-summary-title-id\" class=\"fudis-error-summary__title\">{{\n title || _translationService.getTranslations()().ERROR_SUMMARY.TITLE\n }}</h2>\n <ul class=\"fudis-error-summary__error-list\">\n @for (error of errors; track error.message) {\n <li class=\"fudis-error-summary__error-list__item\">\n <a\n fudisLink\n href=\"\"\n [size]=\"'md'\"\n (click)=\"handleErrorClick($event, error.id)\"\n [title]=\"error.message\"\n ></a>\n </li>\n }\n </ul>\n </fudis-notification>\n }\n}\n", styles: [".fudis-error-summary{margin-bottom:calc(1rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "component", type: NotificationComponent, selector: "fudis-notification", inputs: ["variant", "ariaDescribedby"] }, { kind: "directive", type: LinkDirective, selector: "[fudisLink]", inputs: ["size", "initialFocus", "id", "external", "title"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ErrorSummaryComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-error-summary', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NotificationComponent, LinkDirective], template: "@if (_visibleErrorList(); as errors) {\n @if (errors.length > 0) {\n <fudis-notification\n class=\"fudis-error-summary\"\n [variant]=\"'danger'\"\n [ariaDescribedby]=\"'fudis-error-summary-title-id'\"\n #focusTarget\n >\n <h2 id=\"fudis-error-summary-title-id\" class=\"fudis-error-summary__title\">{{\n title || _translationService.getTranslations()().ERROR_SUMMARY.TITLE\n }}</h2>\n <ul class=\"fudis-error-summary__error-list\">\n @for (error of errors; track error.message) {\n <li class=\"fudis-error-summary__error-list__item\">\n <a\n fudisLink\n href=\"\"\n [size]=\"'md'\"\n (click)=\"handleErrorClick($event, error.id)\"\n [title]=\"error.message\"\n ></a>\n </li>\n }\n </ul>\n </fudis-notification>\n }\n}\n", styles: [".fudis-error-summary{margin-bottom:calc(1rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisInternalErrorSummaryService }, { type: FudisTranslationService }], propDecorators: { _focusTarget: [{
type: ViewChild,
args: ['focusTarget']
}], parentComponent: [{
type: Input,
args: [{ required: true }]
}], title: [{
type: Input
}], formId: [{
type: Input,
args: [{ required: true }]
}], handleUpdatedErrorList: [{
type: Output
}] } });
/**
* Identifies the actions area of an expandable.
*
* Use this directive to position action buttons to a fixed place.
*/
class ExpandableActionsDirective {
constructor() {
this._hostClass = 'fudis-expandable-actions';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExpandableActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: ExpandableActionsDirective, isStandalone: true, selector: "fudis-expandable-actions", host: { properties: { "class": "this._hostClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExpandableActionsDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-expandable-actions' }]
}], propDecorators: { _hostClass: [{
type: HostBinding,
args: ['class']
}] } });
/**
* Identifies the main content area of an expandable.
*
* Use this directive for lazy loaded content.
*/
class ExpandableContentDirective {
constructor(templateRef) {
this.templateRef = templateRef;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExpandableContentDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: ExpandableContentDirective, isStandalone: true, selector: "[fudisExpandableContent]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExpandableContentDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisExpandableContent]' }]
}], ctorParameters: () => [{ type: i0.TemplateRef }] });
/**
* Toggles the visibility of additional content.
*
* Use this component to progressively disclose information without overwhelming the user.
*/
class ExpandableComponent {
constructor(_element, _idService, _errorSummaryService) {
this._element = _element;
this._idService = _idService;
this._errorSummaryService = _errorSummaryService;
/**
* Visual variant of the expandable: 'regular' | 'lite'
*/
this.variant = 'regular';
/**
* Expandable content padding depth
*/
this.padding = 'default';
/**
* Display Expandable title in the breadcrumb of Error Summary
*/
this.errorSummaryBreadcrumb = false;
// TODO: write test
/**
* If Expandable is used inside Form component, by default it will open itself when
* reloadFormErrors is called in Error Summary Service. To disable this behavior, set this to
* false.
*/
this.openOnErrorSummaryReload = true;
/**
* Optional output function when the closed status changes
*/
this.closedChange = new EventEmitter();
/**
* Internal boolean of whether the expandable is currently closed
*/
this._closed = signal(true, ...(ngDevMode ? [{ debugName: "_closed" }] : /* istanbul ignore next */ []));
/**
* Lazy loading check for expanding content
*/
this._openedOnce = signal(false, ...(ngDevMode ? [{ debugName: "_openedOnce" }] : /* istanbul ignore next */ []));
/**
* Is info sent to Error Summary Service
*/
this._errorSummaryInfoSent = false;
this._destroyRef = inject(DestroyRef);
this._injector = inject(Injector);
this._id = this._idService.getNewId('expandable');
}
/**
* Expandable is initially closed by default but can be controlled by [closed] input property
*/
set closed(value) {
this._setClosedStatus(value);
}
_getParentForm() {
this._errorSummaryService
.getFormAncestorId(this._element.nativeElement)
.then((parentFormId) => {
if (!parentFormId)
return;
this._parentFormId = parentFormId;
if (this.errorSummaryBreadcrumb) {
this._addToErrorSummary(this.title);
}
toObservable(this._errorSummaryService.errorSummaryVisibilityStatus[this._parentFormId], {
injector: this._injector,
})
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe((value) => {
if (this.closed && this.openOnErrorSummaryReload && value) {
this._setClosedStatus(false);
}
});
});
}
/**
* Getter for closed boolean
*/
get closed() {
return this._closed();
}
ngAfterContentInit() {
this._getParentForm();
}
ngOnChanges(changes) {
if (changes.title || changes.errorSummaryBreadcrumb?.currentValue === true) {
this._addToErrorSummary(this.title);
}
if (changes?.errorSummaryBreadcrumb?.currentValue === false) {
this._removeFromErrorSummary();
}
}
ngOnDestroy() {
this._removeFromErrorSummary();
}
/**
* Send error object to Error Summary Service
*/
_addToErrorSummary(title) {
if (this.errorSummaryBreadcrumb && this._parentFormId) {
const errorSummaryInfo = {
id: this._id,
formId: this._parentFormId,
title: title,
};
this._errorSummaryService.addSection(errorSummaryInfo);
this._errorSummaryInfoSent = true;
}
}
/**
* Remove error object from Error Summary Service
*/
_removeFromErrorSummary() {
if (this._errorSummaryInfoSent && this._parentFormId) {
this._errorSummaryService.removeSection(this._parentFormId, this._id);
}
}
/**
* Setter for closed boolean
*/
_setClosedStatus(value) {
this._closed.set(value ?? this._closed());
this._openedOnce.set(this._openedOnce() || !this._closed());
this.closedChange.emit(this._closed());
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExpandableComponent, deps: [{ token: i0.ElementRef }, { token: FudisIdService }, { token: FudisInternalErrorSummaryService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ExpandableComponent, isStandalone: true, selector: "fudis-expandable", inputs: { title: "title", level: "level", variant: "variant", padding: "padding", subTitle: "subTitle", badge: "badge", badgeText: "badgeText", errorSummaryBreadcrumb: "errorSummaryBreadcrumb", openOnErrorSummaryReload: "openOnErrorSummaryReload", closed: "closed" }, outputs: { closedChange: "closedChange" }, queries: [{ propertyName: "_content", first: true, predicate: ExpandableContentDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n [id]=\"_id\"\n class=\"fudis-expandable fudis-expandable__{{ variant }}\"\n [class.fudis-expandable--closed]=\"_closed()\"\n>\n <div class=\"fudis-expandable__header fudis-expandable__header__{{ variant }}\">\n <div role=\"heading\" [attr.aria-level]=\"level\" class=\"fudis-expandable__header__heading\">\n <button\n type=\"button\"\n [id]=\"_id + '-header-button'\"\n class=\"fudis-expandable__header__heading__button fudis-expandable__header__heading__button__{{\n variant\n }}\"\n [attr.aria-expanded]=\"!_closed()\"\n [attr.aria-controls]=\"!_closed() ? _id + '__content' : null\"\n (click)=\"_setClosedStatus(!_closed())\"\n >\n <fudis-icon\n class=\"fudis-expandable__header__heading__button__icon fudis-expandable__header__heading__button__icon__{{\n variant\n }}\"\n [icon]=\"variant === 'lite' ? 'chevron' : 'chevron-ring-fill'\"\n [color]=\"variant === 'lite' ? 'primary' : 'gray-dark'\"\n [rotate]=\"_closed() ? 'none' : 'cw-90'\"\n ></fudis-icon\n ><span class=\"fudis-expandable__header__heading__button__title\"\n ><span class=\"fudis-expandable__header__heading__button__title__text\">{{ title }}</span>\n @if (badge && badgeText) {\n <fudis-badge\n class=\"fudis-expandable__header__heading__button__title__badge\"\n [variant]=\"badge\"\n [content]=\"badgeText\"\n />\n }\n @if (variant !== \"lite\" && subTitle) {\n <span class=\"fudis-expandable__header__heading__button__title__sub-heading\">{{\n subTitle\n }}</span>\n }\n </span>\n </button>\n </div>\n <ng-content select=\"fudis-expandable-actions\" />\n </div>\n @if (_openedOnce()) {\n <div\n class=\"fudis-expandable__content fudis-expandable__content__padding-{{ padding }}\"\n [id]=\"_id + '__content'\"\n [attr.aria-hidden]=\"_closed()\"\n [hidden]=\"_closed()\"\n >\n @if (_content) {\n <ng-container *ngTemplateOutlet=\"_content!.templateRef\"></ng-container>\n }\n </div>\n }\n</div>\n", styles: [".fudis-expandable{border-top:0}.fudis-expandable--closed{border-bottom:0}.fudis-expandable__regular{border:2px solid #f1f1f1}.fudis-expandable__header{display:block;padding:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-expandable__header__regular{background-color:#f1f1f1}.fudis-expandable__header__heading{margin:0;width:100%}.fudis-expandable__header__heading__button{border:none;background-color:transparent;line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:400;display:flex;width:100%;text-align:left;cursor:pointer}.fudis-expandable__header__heading__button:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-expandable__header__heading__button__icon__regular{margin-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-expandable__header__heading__button__title{align-self:center}.fudis-expandable__header__heading__button__title__text{margin-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-expandable__header__heading__button__title__sub-heading{font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:300;display:block}.fudis-expandable__header__heading__button__lite{color:var(--fudis-color-primary-dark);display:inline-block;position:relative;text-decoration:underline;overflow-wrap:anywhere;font-family:Fira Sans,sans-serif}.fudis-expandable__header__heading__button__lite:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-expandable__header__heading__button__lite{font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400;color:var(--fudis-color-primary);text-decoration:none}@media screen and (min-width:36em){.fudis-expandable__header{display:flex}}@media screen and (min-width:48em){.fudis-expandable__content__padding-default{padding:calc(1.5rem / var(--fudis-rem-multiplier));padding-left:calc(2.5rem / var(--fudis-rem-multiplier))}}@media screen and (max-width:47.99em){.fudis-expandable__content__padding-default{padding:calc(1rem / var(--fudis-rem-multiplier));padding-left:calc(1.5rem / var(--fudis-rem-multiplier))}}.fudis-expandable__content__padding-small{padding:calc(1rem / var(--fudis-rem-multiplier))}.fudis-expandable-actions{display:flex;gap:calc(.5rem / var(--fudis-rem-multiplier));margin-top:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-expandable-actions fudis-button:first-of-type{margin-left:calc(.5rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-expandable-actions{margin-top:0;margin-left:0}}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }, { kind: "component", type: BadgeComponent, selector: "fudis-badge", inputs: ["variant", "content"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ExpandableComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-expandable', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent, BadgeComponent, NgTemplateOutlet], template: "<div\n [id]=\"_id\"\n class=\"fudis-expandable fudis-expandable__{{ variant }}\"\n [class.fudis-expandable--closed]=\"_closed()\"\n>\n <div class=\"fudis-expandable__header fudis-expandable__header__{{ variant }}\">\n <div role=\"heading\" [attr.aria-level]=\"level\" class=\"fudis-expandable__header__heading\">\n <button\n type=\"button\"\n [id]=\"_id + '-header-button'\"\n class=\"fudis-expandable__header__heading__button fudis-expandable__header__heading__button__{{\n variant\n }}\"\n [attr.aria-expanded]=\"!_closed()\"\n [attr.aria-controls]=\"!_closed() ? _id + '__content' : null\"\n (click)=\"_setClosedStatus(!_closed())\"\n >\n <fudis-icon\n class=\"fudis-expandable__header__heading__button__icon fudis-expandable__header__heading__button__icon__{{\n variant\n }}\"\n [icon]=\"variant === 'lite' ? 'chevron' : 'chevron-ring-fill'\"\n [color]=\"variant === 'lite' ? 'primary' : 'gray-dark'\"\n [rotate]=\"_closed() ? 'none' : 'cw-90'\"\n ></fudis-icon\n ><span class=\"fudis-expandable__header__heading__button__title\"\n ><span class=\"fudis-expandable__header__heading__button__title__text\">{{ title }}</span>\n @if (badge && badgeText) {\n <fudis-badge\n class=\"fudis-expandable__header__heading__button__title__badge\"\n [variant]=\"badge\"\n [content]=\"badgeText\"\n />\n }\n @if (variant !== \"lite\" && subTitle) {\n <span class=\"fudis-expandable__header__heading__button__title__sub-heading\">{{\n subTitle\n }}</span>\n }\n </span>\n </button>\n </div>\n <ng-content select=\"fudis-expandable-actions\" />\n </div>\n @if (_openedOnce()) {\n <div\n class=\"fudis-expandable__content fudis-expandable__content__padding-{{ padding }}\"\n [id]=\"_id + '__content'\"\n [attr.aria-hidden]=\"_closed()\"\n [hidden]=\"_closed()\"\n >\n @if (_content) {\n <ng-container *ngTemplateOutlet=\"_content!.templateRef\"></ng-container>\n }\n </div>\n }\n</div>\n", styles: [".fudis-expandable{border-top:0}.fudis-expandable--closed{border-bottom:0}.fudis-expandable__regular{border:2px solid #f1f1f1}.fudis-expandable__header{display:block;padding:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-expandable__header__regular{background-color:#f1f1f1}.fudis-expandable__header__heading{margin:0;width:100%}.fudis-expandable__header__heading__button{border:none;background-color:transparent;line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:400;display:flex;width:100%;text-align:left;cursor:pointer}.fudis-expandable__header__heading__button:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-expandable__header__heading__button__icon__regular{margin-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-expandable__header__heading__button__title{align-self:center}.fudis-expandable__header__heading__button__title__text{margin-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-expandable__header__heading__button__title__sub-heading{font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:300;display:block}.fudis-expandable__header__heading__button__lite{color:var(--fudis-color-primary-dark);display:inline-block;position:relative;text-decoration:underline;overflow-wrap:anywhere;font-family:Fira Sans,sans-serif}.fudis-expandable__header__heading__button__lite:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-expandable__header__heading__button__lite{font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400;color:var(--fudis-color-primary);text-decoration:none}@media screen and (min-width:36em){.fudis-expandable__header{display:flex}}@media screen and (min-width:48em){.fudis-expandable__content__padding-default{padding:calc(1.5rem / var(--fudis-rem-multiplier));padding-left:calc(2.5rem / var(--fudis-rem-multiplier))}}@media screen and (max-width:47.99em){.fudis-expandable__content__padding-default{padding:calc(1rem / var(--fudis-rem-multiplier));padding-left:calc(1.5rem / var(--fudis-rem-multiplier))}}.fudis-expandable__content__padding-small{padding:calc(1rem / var(--fudis-rem-multiplier))}.fudis-expandable-actions{display:flex;gap:calc(.5rem / var(--fudis-rem-multiplier));margin-top:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-expandable-actions fudis-button:first-of-type{margin-left:calc(.5rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-expandable-actions{margin-top:0;margin-left:0}}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: FudisIdService }, { type: FudisInternalErrorSummaryService }], propDecorators: { _content: [{
type: ContentChild,
args: [ExpandableContentDirective]
}], title: [{
type: Input,
args: [{ required: true }]
}], level: [{
type: Input,
args: [{ required: true }]
}], variant: [{
type: Input
}], padding: [{
type: Input
}], subTitle: [{
type: Input
}], badge: [{
type: Input
}], badgeText: [{
type: Input
}], errorSummaryBreadcrumb: [{
type: Input
}], openOnErrorSummaryReload: [{
type: Input
}], closed: [{
type: Input
}], closedChange: [{
type: Output
}] } });
/**
* Displays footer content for a page.
*
* Use this component with supplementary information such as links, legal content, or metadata.
*/
class FooterComponent {
constructor(_translationService) {
this._translationService = _translationService;
/**
* Alternative text for the Funidata logo
*/
this._funidataLogoAltText = new BehaviorSubject(this._translationService.getTranslations()().IMAGE.FUNIDATA_LOGO);
/**
* External link text for Funidata logo
*/
this._externalLinkHelpText = new BehaviorSubject(this._translationService.getTranslations()().LINK.EXTERNAL_LINK);
effect(() => {
const translations = this._translationService.getTranslations()();
this._funidataLogoAltText.next(translations.IMAGE.FUNIDATA_LOGO);
this._externalLinkHelpText.next(translations.LINK.EXTERNAL_LINK);
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FooterComponent, deps: [{ token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: FooterComponent, isStandalone: true, selector: "fudis-footer", ngImport: i0, template: "<footer class=\"fudis-footer\">\n <div class=\"fudis-footer__item\">\n <a\n href=\"https://www.funidata.fi/\"\n class=\"fudis-footer__item__logo\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n [attr.aria-label]=\"(_funidataLogoAltText | async) + ' ' + (_externalLinkHelpText | async)\"\n ><svg\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n width=\"127\"\n height=\"24\"\n viewBox=\"0 0 127 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <title>Funidata logo</title>\n <path\n d=\"M10.5893 4.93128H3.64757V9.71953H10.3425V12.5456H3.64757V22.0295H0.642578V2.1114H10.5893V4.93128ZM16.4203 2.1114V14.1067C16.4203 15.8159 16.698 17.0685 17.2595 17.8583C18.0925 18.9998 19.2649 19.5737 20.7828 19.5737C22.3008 19.5737 23.4855 18.9998 24.3185 17.8583C24.88 17.0932 25.1577 15.8406 25.1577 14.1067V2.1114H28.1627V14.9335C28.1627 17.0315 27.5086 18.7592 26.2005 20.1229C24.7319 21.6346 22.924 22.3936 20.7828 22.3936C18.6417 22.3936 16.8399 21.6346 15.3775 20.1229C14.0694 18.7654 13.4154 17.0376 13.4154 14.9335V2.1114H16.4203V2.1114ZM32.6239 22.0357V0.753906L47.149 15.9517V2.1114H50.154V23.2451L35.635 8.08437V22.0295H32.63L32.6239 22.0357ZM57.6943 2.1114V22.0295H54.6893V2.1114H57.6943ZM74.5519 4.67829C73.5399 3.74656 72.4046 3.0925 71.1582 2.70376C69.9981 2.30885 68.4123 2.1114 66.4069 2.1114H62.2234V15.8714H65.2284V4.93128H66.5859C69.1466 4.93128 71.0903 5.53598 72.4046 6.75156C73.867 8.10905 74.5951 9.87996 74.5951 12.0643C74.5951 14.2486 73.8546 15.9887 72.3737 17.34C71.6518 17.9941 70.8435 18.4692 69.9487 18.7715C69.054 19.0554 67.931 19.1973 66.5797 19.1973H62.2172V22.0233H66.3452C68.3691 22.0233 69.9241 21.8382 71.0286 21.468C72.2071 21.0978 73.3363 20.4376 74.41 19.4935C76.5511 17.5992 77.6248 15.1248 77.6248 12.0643C77.6248 9.00376 76.5943 6.54793 74.5395 4.67212L74.5519 4.67829ZM91.7982 17.1919H83.2707L81.0493 22.0295H77.8099L87.6702 0.858803L97.1788 22.0295H93.89L91.7982 17.1919V17.1919ZM90.5703 14.3659L87.6147 7.59073L84.5171 14.3659H90.5703ZM103.726 4.93128V22.0295H100.721V4.93128H96.1422V2.1114H108.298V4.93128H103.732H103.726ZM121.256 17.1857H112.728L110.507 22.0233H107.267L117.134 0.858803L126.643 22.0295H123.354L121.262 17.1919L121.256 17.1857ZM120.028 14.3597L117.072 7.58456L113.975 14.3597H120.028Z\"\n fill=\"currentColor\"\n ></path>\n </svg>\n </a>\n </div>\n <div class=\"fudis-footer__item\">\n <ng-content></ng-content>\n </div>\n</footer>\n", styles: [".fudis-footer{box-sizing:border-box;margin:0;padding:0;background-color:#f1f1f1;display:inline-flex;position:relative;flex-direction:column;align-items:center;padding:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier)) calc(4rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));width:100%;text-align:center}.fudis-footer__item:first-of-type{margin-bottom:calc(1rem / var(--fudis-rem-multiplier))}.fudis-footer__item__logo{display:block;color:#000}.fudis-footer__item__logo:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}@media screen and (min-width:36em){.fudis-footer__item:first-of-type{margin-right:calc(2.5rem / var(--fudis-rem-multiplier));margin-bottom:0}}@media screen and (min-width:36em){.fudis-footer{flex-direction:row;align-items:baseline;justify-content:space-between;padding-right:calc(4rem / var(--fudis-rem-multiplier));padding-left:calc(4rem / var(--fudis-rem-multiplier));text-align:start}}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FooterComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-footer', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AsyncPipe], template: "<footer class=\"fudis-footer\">\n <div class=\"fudis-footer__item\">\n <a\n href=\"https://www.funidata.fi/\"\n class=\"fudis-footer__item__logo\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n [attr.aria-label]=\"(_funidataLogoAltText | async) + ' ' + (_externalLinkHelpText | async)\"\n ><svg\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n width=\"127\"\n height=\"24\"\n viewBox=\"0 0 127 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <title>Funidata logo</title>\n <path\n d=\"M10.5893 4.93128H3.64757V9.71953H10.3425V12.5456H3.64757V22.0295H0.642578V2.1114H10.5893V4.93128ZM16.4203 2.1114V14.1067C16.4203 15.8159 16.698 17.0685 17.2595 17.8583C18.0925 18.9998 19.2649 19.5737 20.7828 19.5737C22.3008 19.5737 23.4855 18.9998 24.3185 17.8583C24.88 17.0932 25.1577 15.8406 25.1577 14.1067V2.1114H28.1627V14.9335C28.1627 17.0315 27.5086 18.7592 26.2005 20.1229C24.7319 21.6346 22.924 22.3936 20.7828 22.3936C18.6417 22.3936 16.8399 21.6346 15.3775 20.1229C14.0694 18.7654 13.4154 17.0376 13.4154 14.9335V2.1114H16.4203V2.1114ZM32.6239 22.0357V0.753906L47.149 15.9517V2.1114H50.154V23.2451L35.635 8.08437V22.0295H32.63L32.6239 22.0357ZM57.6943 2.1114V22.0295H54.6893V2.1114H57.6943ZM74.5519 4.67829C73.5399 3.74656 72.4046 3.0925 71.1582 2.70376C69.9981 2.30885 68.4123 2.1114 66.4069 2.1114H62.2234V15.8714H65.2284V4.93128H66.5859C69.1466 4.93128 71.0903 5.53598 72.4046 6.75156C73.867 8.10905 74.5951 9.87996 74.5951 12.0643C74.5951 14.2486 73.8546 15.9887 72.3737 17.34C71.6518 17.9941 70.8435 18.4692 69.9487 18.7715C69.054 19.0554 67.931 19.1973 66.5797 19.1973H62.2172V22.0233H66.3452C68.3691 22.0233 69.9241 21.8382 71.0286 21.468C72.2071 21.0978 73.3363 20.4376 74.41 19.4935C76.5511 17.5992 77.6248 15.1248 77.6248 12.0643C77.6248 9.00376 76.5943 6.54793 74.5395 4.67212L74.5519 4.67829ZM91.7982 17.1919H83.2707L81.0493 22.0295H77.8099L87.6702 0.858803L97.1788 22.0295H93.89L91.7982 17.1919V17.1919ZM90.5703 14.3659L87.6147 7.59073L84.5171 14.3659H90.5703ZM103.726 4.93128V22.0295H100.721V4.93128H96.1422V2.1114H108.298V4.93128H103.732H103.726ZM121.256 17.1857H112.728L110.507 22.0233H107.267L117.134 0.858803L126.643 22.0295H123.354L121.262 17.1919L121.256 17.1857ZM120.028 14.3597L117.072 7.58456L113.975 14.3597H120.028Z\"\n fill=\"currentColor\"\n ></path>\n </svg>\n </a>\n </div>\n <div class=\"fudis-footer__item\">\n <ng-content></ng-content>\n </div>\n</footer>\n", styles: [".fudis-footer{box-sizing:border-box;margin:0;padding:0;background-color:#f1f1f1;display:inline-flex;position:relative;flex-direction:column;align-items:center;padding:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier)) calc(4rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));width:100%;text-align:center}.fudis-footer__item:first-of-type{margin-bottom:calc(1rem / var(--fudis-rem-multiplier))}.fudis-footer__item__logo{display:block;color:#000}.fudis-footer__item__logo:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}@media screen and (min-width:36em){.fudis-footer__item:first-of-type{margin-right:calc(2.5rem / var(--fudis-rem-multiplier));margin-bottom:0}}@media screen and (min-width:36em){.fudis-footer{flex-direction:row;align-items:baseline;justify-content:space-between;padding-right:calc(4rem / var(--fudis-rem-multiplier));padding-left:calc(4rem / var(--fudis-rem-multiplier));text-align:start}}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }] });
/**
* Get corresponding default variant for heading level
*/
const getHeadingVariant = (level) => {
switch (level) {
case 1:
return 'xxl';
case 2:
return 'xl';
case 3:
return 'lg';
case 4:
return 'md';
case 5:
return 'sm';
case 6:
return 'xs';
default:
return 'lg';
}
};
/**
* Displays a heading at a specified semantic level (h1-h6).
*
* Use this component to structure content hierarchially and provide semantic information for all
* users.
*/
class HeadingComponent {
constructor(_idService, _wrapperElement) {
this._idService = _idService;
this._wrapperElement = _wrapperElement;
/**
* Align heading: 'center' | 'left' | 'right'
*/
this.align = 'left';
/**
* Heading CSS class list
*/
this._classList = new BehaviorSubject([]);
/**
* Set Heading wrapper to be "full width" if used inside Grid
*/
_wrapperElement.nativeElement.style.gridColumn = '1/-1';
}
/**
* Set CSS classes for heading
*/
_setClasses() {
const calcVariant = this.variant || getHeadingVariant(this.level);
const newClasses = [
`fudis-heading`,
`fudis-heading__variant__${calcVariant}`,
`fudis-heading__align__${this.align}`,
];
this._classList.next(newClasses);
}
ngOnInit() {
this._id = this.id ?? this._idService.getNewId('heading');
this._setClasses();
}
ngOnChanges(changes) {
if (changes.align?.currentValue !== changes.align?.previousValue ||
changes.variant?.currentValue !== changes.variant?.previousValue ||
changes.level?.currentValue !== changes.level?.previousValue) {
this._setClasses();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HeadingComponent, deps: [{ token: FudisIdService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: HeadingComponent, isStandalone: true, selector: "fudis-heading", inputs: { level: "level", variant: "variant", id: "id", align: "align" }, viewQueries: [{ propertyName: "headingRef", first: true, predicate: ["headingRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<ng-container\n *ngTemplateOutlet=\"\n level === 1\n ? h1\n : level === 2\n ? h2\n : level === 3\n ? h3\n : level === 4\n ? h4\n : level === 5\n ? h5\n : h6\n \"\n></ng-container>\n\n<!-- From level @Input choose which h-tag to render -->\n<ng-template #h1>\n <h1 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h1>\n</ng-template>\n\n<ng-template #h2>\n <h2 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h2>\n</ng-template>\n\n<ng-template #h3>\n <h3 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h3>\n</ng-template>\n\n<ng-template #h4>\n <h4 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h4>\n</ng-template>\n\n<ng-template #h5>\n <h5 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h5>\n</ng-template>\n\n<ng-template #h6>\n <h6 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h6>\n</ng-template>\n\n<!-- Render content inside fudis-heading tags -->\n<ng-template #headingContent>\n <ng-content></ng-content>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HeadingComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-heading', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, AsyncPipe], template: "<ng-container\n *ngTemplateOutlet=\"\n level === 1\n ? h1\n : level === 2\n ? h2\n : level === 3\n ? h3\n : level === 4\n ? h4\n : level === 5\n ? h5\n : h6\n \"\n></ng-container>\n\n<!-- From level @Input choose which h-tag to render -->\n<ng-template #h1>\n <h1 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h1>\n</ng-template>\n\n<ng-template #h2>\n <h2 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h2>\n</ng-template>\n\n<ng-template #h3>\n <h3 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h3>\n</ng-template>\n\n<ng-template #h4>\n <h4 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h4>\n</ng-template>\n\n<ng-template #h5>\n <h5 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h5>\n</ng-template>\n\n<ng-template #h6>\n <h6 #headingRef [id]=\"_id\" tabindex=\"-1\" [class]=\"_classList | async\">\n <ng-container *ngTemplateOutlet=\"headingContent\"></ng-container>\n </h6>\n</ng-template>\n\n<!-- Render content inside fudis-heading tags -->\n<ng-template #headingContent>\n <ng-content></ng-content>\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: i0.ElementRef }], propDecorators: { headingRef: [{
type: ViewChild,
args: ['headingRef']
}], level: [{
type: Input,
args: [{ required: true }]
}], variant: [{
type: Input
}], id: [{
type: Input
}], align: [{
type: Input
}] } });
/**
* Provides layout and structure for form content. Extends Grid — all Grid inputs are available.
*
* Use this component to consistently arrange form fields, actions, and validation elements. Place
* fields inside `<fudis-form-content>` and buttons inside `<fudis-form-actions>`. Use
* `fudisFormSubmit` directive on the submit button to trigger error summary. Use `FudisValidators`
* (not Angular's Validators) for error messages to work with Error Summary.
*/
class FormComponent extends GridApiDirective {
constructor(_idService, _elementRef, _errorSummaryService, _dialogParent) {
super();
this._idService = _idService;
this._elementRef = _elementRef;
this._errorSummaryService = _errorSummaryService;
this._dialogParent = _dialogParent;
/**
* Set Error Summary visibility manually. Usually set true on form submit with Button binded with
* 'fudisFormSubmit' directive.
*/
this.errorSummaryVisible = false;
/**
* Each time Error Summary List is updated, this emitter will output the list
*/
this.handleUpdatedErrorList = new EventEmitter();
/**
* The _errorSummaryVisible signal tracks the current visibility of the error summary for template
* binding
*/
this._errorSummaryVisible = signal(false, ...(ngDevMode ? [{ debugName: "_errorSummaryVisible" }] : /* istanbul ignore next */ []));
this._injector = inject(Injector);
}
ngOnInit() {
this._setFormId();
if (!this.titleVariant) {
this.titleVariant = getHeadingVariant(this.level);
}
this._errorSummaryService.registerNewForm(this.id, this.errorSummaryVisible);
this._errorSummaryVisible.set(this.errorSummaryVisible);
if (this._dialogParent) {
this._dialogParent.closeButtonPositionAbsolute.set(true);
}
toObservable(this._errorSummaryService.errorSummaryVisibilityStatus[this.id], {
injector: this._injector,
}).subscribe((value) => {
this._errorSummaryVisible.set(value);
});
}
ngOnDestroy() {
this._errorSummaryService.removeForm(this.id);
}
ngOnChanges(changes) {
if (changes.errorSummaryVisible?.currentValue !== changes.errorSummaryVisible?.previousValue &&
this.id) {
this._errorSummaryService.setErrorSummaryVisibility(this.id, this.errorSummaryVisible);
this._errorSummaryVisible.set(this.errorSummaryVisible);
}
}
/**
* Add or generate id with IdService
*/
_setFormId() {
if (this.id) {
this._idService.addNewId('form', this.id);
}
else {
this.id = this._idService.getNewId('form');
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormComponent, deps: [{ token: FudisIdService }, { token: i0.ElementRef }, { token: FudisInternalErrorSummaryService }, { token: DialogComponent, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: FormComponent, isStandalone: true, selector: "fudis-form", inputs: { title: "title", level: "level", id: "id", titleVariant: "titleVariant", helpText: "helpText", badge: "badge", badgeText: "badgeText", errorSummaryVisible: "errorSummaryVisible", errorSummaryTitle: "errorSummaryTitle" }, outputs: { handleUpdatedErrorList: "handleUpdatedErrorList" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<!-- prettier-ignore -->\n<form\n fudisGrid\n [id]=\"id\"\n [attr.aria-describedby]=\"id + '-header'\"\n [width]=\"width\"\n [align]=\"align\"\n [serviceDefaults]=\"false\"\n [alignItemsX]=\"alignItemsX\"\n [alignItemsY]=\"alignItemsY\"\n [classes]=\"_dialogParent ? 'fudis-form fudis-form__dialog' : 'fudis-form fudis-form__default'\"\n [columnGap]=\"columnGap\"\n [rowGap]=\"'none'\"\n>\n <div [id]=\"id + '-header'\" class=\"fudis-form__header\">\n <div class=\"fudis-form__header__main\">\n <div class=\"fudis-form__header__title\">\n @if (_dialogParent) {\n <fudis-heading\n class=\"fudis-form__header__title__dialog fudis-mb-xs\"\n fudisDialogTitle\n [variant]=\"titleVariant\"\n [level]=\"level\"\n >{{ title }}@if (badge && badgeText) {\n <fudis-badge\n class=\"fudis-form__header__title__badge\"\n [variant]=\"badge\"\n [content]=\"badgeText\"\n />\n }\n </fudis-heading>\n }\n @if (!_dialogParent) {\n <fudis-heading class=\"fudis-mb-xs\" [variant]=\"titleVariant\" [level]=\"level\"\n >{{ title }}@if (badge && badgeText) {\n <fudis-badge\n class=\"fudis-form__header__title__badge\"\n [variant]=\"badge\"\n [content]=\"badgeText\"\n />\n }\n </fudis-heading>\n }\n @if (helpText) {\n <fudis-body-text class=\"fudis-mb-xs\">{{ helpText }}</fudis-body-text>\n }\n </div>\n <ng-content select=\"fudis-form-header\" />\n @if (!_dialogParent) {\n <ng-container *ngTemplateOutlet=\"errorSummary\" />\n }\n </div>\n </div>\n @if (!_dialogParent) {\n <ng-container *ngTemplateOutlet=\"actions\" />\n }\n <div class=\"fudis-form__content-wrapper\">\n @if (_dialogParent) {\n <ng-container *ngTemplateOutlet=\"errorSummary\" />\n }\n <ng-content select=\"fudis-form-content\"></ng-content>\n </div>\n @if (_dialogParent) {\n <ng-container *ngTemplateOutlet=\"actions\" />\n }\n</form>\n\n<ng-template #errorSummary>\n @if (_elementRef.nativeElement && _errorSummaryVisible()) {\n <fudis-error-summary\n [formId]=\"id\"\n (handleUpdatedErrorList)=\"handleUpdatedErrorList.emit($event)\"\n [parentComponent]=\"_elementRef.nativeElement\"\n [title]=\"errorSummaryTitle\"\n />\n }\n</ng-template>\n\n<ng-template #actions>\n <ng-content select=\"fudis-form-actions\" />\n</ng-template>\n", styles: [".fudis-form__header__title__badge{margin-left:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-form__header__title__dialog{max-width:calc(100% - 2rem / var(--fudis-rem-multiplier))}.fudis-form__header__main fudis-form-header{display:block;margin-bottom:calc(1rem / var(--fudis-rem-multiplier))}.fudis-form-content{display:block}.fudis-form .fudis-form-actions{display:flex;flex-wrap:wrap}.fudis-form__default .fudis-form-actions{gap:calc(.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));margin-right:calc(.5rem / var(--fudis-rem-multiplier));margin-bottom:calc(1rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-form__default .fudis-form-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-bottom:calc(1.5rem / var(--fudis-rem-multiplier))}.fudis-form__default .fudis-form-actions fudis-button:not(:first-of-type){margin-bottom:0}}.fudis-form__dialog .fudis-form-actions{justify-content:flex-end}.fudis-form__dialog .fudis-form-actions fudis-button:not(:first-of-type){margin-left:calc(.5rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: GridDirective, selector: "[fudisGrid]" }, { kind: "component", type: HeadingComponent, selector: "fudis-heading", inputs: ["level", "variant", "id", "align"] }, { kind: "directive", type: DialogTitleDirective, selector: "[fudisDialogTitle]" }, { kind: "component", type: BadgeComponent, selector: "fudis-badge", inputs: ["variant", "content"] }, { kind: "component", type: BodyTextComponent, selector: "fudis-body-text", inputs: ["variant", "align", "lang"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ErrorSummaryComponent, selector: "fudis-error-summary", inputs: ["parentComponent", "title", "formId"], outputs: ["handleUpdatedErrorList"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-form', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
FormsModule,
GridDirective,
HeadingComponent,
DialogTitleDirective,
BadgeComponent,
BodyTextComponent,
NgTemplateOutlet,
ErrorSummaryComponent,
], template: "<!-- prettier-ignore -->\n<form\n fudisGrid\n [id]=\"id\"\n [attr.aria-describedby]=\"id + '-header'\"\n [width]=\"width\"\n [align]=\"align\"\n [serviceDefaults]=\"false\"\n [alignItemsX]=\"alignItemsX\"\n [alignItemsY]=\"alignItemsY\"\n [classes]=\"_dialogParent ? 'fudis-form fudis-form__dialog' : 'fudis-form fudis-form__default'\"\n [columnGap]=\"columnGap\"\n [rowGap]=\"'none'\"\n>\n <div [id]=\"id + '-header'\" class=\"fudis-form__header\">\n <div class=\"fudis-form__header__main\">\n <div class=\"fudis-form__header__title\">\n @if (_dialogParent) {\n <fudis-heading\n class=\"fudis-form__header__title__dialog fudis-mb-xs\"\n fudisDialogTitle\n [variant]=\"titleVariant\"\n [level]=\"level\"\n >{{ title }}@if (badge && badgeText) {\n <fudis-badge\n class=\"fudis-form__header__title__badge\"\n [variant]=\"badge\"\n [content]=\"badgeText\"\n />\n }\n </fudis-heading>\n }\n @if (!_dialogParent) {\n <fudis-heading class=\"fudis-mb-xs\" [variant]=\"titleVariant\" [level]=\"level\"\n >{{ title }}@if (badge && badgeText) {\n <fudis-badge\n class=\"fudis-form__header__title__badge\"\n [variant]=\"badge\"\n [content]=\"badgeText\"\n />\n }\n </fudis-heading>\n }\n @if (helpText) {\n <fudis-body-text class=\"fudis-mb-xs\">{{ helpText }}</fudis-body-text>\n }\n </div>\n <ng-content select=\"fudis-form-header\" />\n @if (!_dialogParent) {\n <ng-container *ngTemplateOutlet=\"errorSummary\" />\n }\n </div>\n </div>\n @if (!_dialogParent) {\n <ng-container *ngTemplateOutlet=\"actions\" />\n }\n <div class=\"fudis-form__content-wrapper\">\n @if (_dialogParent) {\n <ng-container *ngTemplateOutlet=\"errorSummary\" />\n }\n <ng-content select=\"fudis-form-content\"></ng-content>\n </div>\n @if (_dialogParent) {\n <ng-container *ngTemplateOutlet=\"actions\" />\n }\n</form>\n\n<ng-template #errorSummary>\n @if (_elementRef.nativeElement && _errorSummaryVisible()) {\n <fudis-error-summary\n [formId]=\"id\"\n (handleUpdatedErrorList)=\"handleUpdatedErrorList.emit($event)\"\n [parentComponent]=\"_elementRef.nativeElement\"\n [title]=\"errorSummaryTitle\"\n />\n }\n</ng-template>\n\n<ng-template #actions>\n <ng-content select=\"fudis-form-actions\" />\n</ng-template>\n", styles: [".fudis-form__header__title__badge{margin-left:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-form__header__title__dialog{max-width:calc(100% - 2rem / var(--fudis-rem-multiplier))}.fudis-form__header__main fudis-form-header{display:block;margin-bottom:calc(1rem / var(--fudis-rem-multiplier))}.fudis-form-content{display:block}.fudis-form .fudis-form-actions{display:flex;flex-wrap:wrap}.fudis-form__default .fudis-form-actions{gap:calc(.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));margin-right:calc(.5rem / var(--fudis-rem-multiplier));margin-bottom:calc(1rem / var(--fudis-rem-multiplier))}@media screen and (min-width:36em){.fudis-form__default .fudis-form-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-bottom:calc(1.5rem / var(--fudis-rem-multiplier))}.fudis-form__default .fudis-form-actions fudis-button:not(:first-of-type){margin-bottom:0}}.fudis-form__dialog .fudis-form-actions{justify-content:flex-end}.fudis-form__dialog .fudis-form-actions fudis-button:not(:first-of-type){margin-left:calc(.5rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: i0.ElementRef }, { type: FudisInternalErrorSummaryService }, { type: DialogComponent, decorators: [{
type: Host
}, {
type: Optional
}] }], propDecorators: { title: [{
type: Input,
args: [{ required: true }]
}], level: [{
type: Input,
args: [{ required: true }]
}], id: [{
type: Input
}], titleVariant: [{
type: Input
}], helpText: [{
type: Input
}], badge: [{
type: Input
}], badgeText: [{
type: Input
}], errorSummaryVisible: [{
type: Input
}], errorSummaryTitle: [{
type: Input
}], handleUpdatedErrorList: [{
type: Output
}] } });
/**
* Provides a responsive grid layout.
*
* Use this component to structure page or section layouts visually using columns.
*/
/**
* Just a wrapper component to be able to use Grid in templates using '<fudis-grid></fudis-grid>'
* tags. Main functionality is in GridDirective
*/
class GridComponent extends GridDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: GridComponent, isStandalone: true, selector: "fudis-grid", usesInheritance: true, ngImport: i0, template: "<ng-content></ng-content>\n", styles: [".fudis-grid{box-sizing:border-box;display:grid;grid-template-rows:auto 1fr;grid-template-columns:auto 1fr}@media screen and (min-width:36em){.fudis-grid{width:100%}}.fudis-grid__align__start{margin-right:auto;margin-left:0}.fudis-grid__align__center{margin-right:auto;margin-left:auto}.fudis-grid__align__end{margin-right:0;margin-left:auto}@media screen and (min-width:0){.fudis-grid__xxl{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__xxl{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-grid__xxl{max-width:calc(45rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:62em){.fudis-grid__xxl{max-width:calc(60rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:75em){.fudis-grid__xxl{max-width:calc(65rem / var(--fudis-rem-multiplier));gap:calc(2rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:100em){.fudis-grid__xxl{max-width:calc(96.25rem / var(--fudis-rem-multiplier));gap:calc(2rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:0){.fudis-grid__xl{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__xl{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-grid__xl{max-width:calc(45rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:62em){.fudis-grid__xl{max-width:calc(60rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:75em){.fudis-grid__xl{max-width:calc(65rem / var(--fudis-rem-multiplier));gap:calc(2rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:0){.fudis-grid__lg{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__lg{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-grid__lg{max-width:calc(45rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:62em){.fudis-grid__lg{max-width:calc(60rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:0){.fudis-grid__md{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__md{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-grid__md{max-width:calc(45rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:0){.fudis-grid__sm{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__sm{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}.fudis-grid__xs{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}.fudis-grid__initial{width:initial}.fudis-grid__full-width{width:100%}.fudis-grid__row-gap__none{row-gap:0}.fudis-grid__row-gap__xxs{row-gap:calc(.25rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__xs{row-gap:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__sm{row-gap:calc(1rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__md{row-gap:calc(1.5rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__lg{row-gap:calc(2rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__xl{row-gap:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__xxl{row-gap:calc(4rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__none{column-gap:0}.fudis-grid__column-gap__xxs{column-gap:calc(.25rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__xs{column-gap:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__sm{column-gap:calc(1rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__md{column-gap:calc(1.5rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__lg{column-gap:calc(2rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__xl{column-gap:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__xxl{column-gap:calc(4rem / var(--fudis-rem-multiplier))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-grid', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>\n", styles: [".fudis-grid{box-sizing:border-box;display:grid;grid-template-rows:auto 1fr;grid-template-columns:auto 1fr}@media screen and (min-width:36em){.fudis-grid{width:100%}}.fudis-grid__align__start{margin-right:auto;margin-left:0}.fudis-grid__align__center{margin-right:auto;margin-left:auto}.fudis-grid__align__end{margin-right:0;margin-left:auto}@media screen and (min-width:0){.fudis-grid__xxl{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__xxl{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-grid__xxl{max-width:calc(45rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:62em){.fudis-grid__xxl{max-width:calc(60rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:75em){.fudis-grid__xxl{max-width:calc(65rem / var(--fudis-rem-multiplier));gap:calc(2rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:100em){.fudis-grid__xxl{max-width:calc(96.25rem / var(--fudis-rem-multiplier));gap:calc(2rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:0){.fudis-grid__xl{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__xl{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-grid__xl{max-width:calc(45rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:62em){.fudis-grid__xl{max-width:calc(60rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:75em){.fudis-grid__xl{max-width:calc(65rem / var(--fudis-rem-multiplier));gap:calc(2rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:0){.fudis-grid__lg{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__lg{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-grid__lg{max-width:calc(45rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:62em){.fudis-grid__lg{max-width:calc(60rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:0){.fudis-grid__md{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__md{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:48em){.fudis-grid__md{max-width:calc(45rem / var(--fudis-rem-multiplier));gap:calc(1.5rem / var(--fudis-rem-multiplier)) calc(1.5rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:0){.fudis-grid__sm{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}@media screen and (min-width:36em){.fudis-grid__sm{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}}.fudis-grid__xs{max-width:calc(34rem / var(--fudis-rem-multiplier));gap:calc(1rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier))}.fudis-grid__initial{width:initial}.fudis-grid__full-width{width:100%}.fudis-grid__row-gap__none{row-gap:0}.fudis-grid__row-gap__xxs{row-gap:calc(.25rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__xs{row-gap:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__sm{row-gap:calc(1rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__md{row-gap:calc(1.5rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__lg{row-gap:calc(2rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__xl{row-gap:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-grid__row-gap__xxl{row-gap:calc(4rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__none{column-gap:0}.fudis-grid__column-gap__xxs{column-gap:calc(.25rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__xs{column-gap:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__sm{column-gap:calc(1rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__md{column-gap:calc(1.5rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__lg{column-gap:calc(2rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__xl{column-gap:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-grid__column-gap__xxl{column-gap:calc(4rem / var(--fudis-rem-multiplier))}\n"] }]
}] });
/**
* Applies grid item behavior within a grid container.
*
* Use this directive to control placement and span of child elements inside a grid container.
*/
class GridItemDirective {
constructor(_gridItemElement, _breakpointService) {
this._gridItemElement = _gridItemElement;
this._breakpointService = _breakpointService;
/**
* Apply CSS grid-column values for the Grid Item
*/
this._calculatedColumns = gridItemDefault;
/**
* Apply horizontal CSS values for the Grid Item
*/
this._calculatedAlignX = 'stretch';
/**
* Apply vertical CSS values for the Grid Item
*/
this._calculatedAlignY = 'stretch';
this._element = _gridItemElement.nativeElement;
/**
* When screen is resized check and apply new rules for Grid Item
*/
effect(() => {
this._breakpointService.getBreakpointState();
if (typeof this._calculatedColumns !== 'string') {
this._setColumns();
}
if (typeof this._calculatedAlignX !== 'string') {
this._setAlignX();
}
if (typeof this._calculatedAlignY !== 'string') {
this._setAlignY();
}
});
}
/**
* Set columns for single Grid Item
*/
set columns(value) {
// Convert given string value to proper CSS grid-column value
if (typeof value === 'string') {
this._calculatedColumns = getGridCssValue(value, true);
}
// Convert given number value to proper CSS grid-column value. E.g. number 6 converts to 'span 6'.
else if (typeof value === 'number') {
this._calculatedColumns = getGridCssValue(value, true);
}
// Get breakpoint settings with provided values
else {
this._calculatedColumns = getBreakpointDataArray(value, gridItemDefault, true);
}
}
/**
* Align Grid Item horizontally
*/
set alignSelfX(value) {
if (typeof value === 'string') {
this._calculatedAlignX = value;
}
else {
this._calculatedAlignX = getBreakpointDataArray(value, 'stretch');
}
}
/**
* Align Grid Item vertically
*/
set alignSelfY(value) {
if (typeof value === 'string') {
this._calculatedAlignY = value;
}
else {
this._calculatedAlignY = getBreakpointDataArray(value, 'stretch');
}
}
ngOnInit() {
this._element.classList.add('fudis-grid-item');
this._applyGridItemCss();
}
ngOnChanges() {
this._applyGridItemCss();
}
/**
* Set CSS grid-column attributes for this Grid Item element
*/
_setColumns() {
this._breakpointService.setStyleAttributes(this._element, 'grid-column', this._calculatedColumns);
}
/**
* Set CSS justify-self attributes for this Grid Item element
*/
_setAlignX() {
this._breakpointService.setStyleAttributes(this._element, 'justify-self', this._calculatedAlignX);
}
/**
* Set CSS align-self attributes for this Grid Item element
*/
_setAlignY() {
this._breakpointService.setStyleAttributes(this._element, 'align-self', this._calculatedAlignY);
}
/**
* Apply CSS settings from Inputs
*/
_applyGridItemCss() {
this._setColumns();
this._setAlignX();
this._setAlignY();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridItemDirective, deps: [{ token: i0.ElementRef }, { token: FudisBreakpointService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: GridItemDirective, isStandalone: true, selector: "[fudisGridItem]", inputs: { columns: "columns", alignSelfX: "alignSelfX", alignSelfY: "alignSelfY" }, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridItemDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisGridItem]' }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: FudisBreakpointService }], propDecorators: { columns: [{
type: Input
}], alignSelfX: [{
type: Input
}], alignSelfY: [{
type: Input
}] } });
/**
* Represents a single item within a grid layout.
*
* Use this component to define column span and placement inside a grid container.
*/
class GridItemComponent extends GridItemDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: GridItemComponent, isStandalone: true, selector: "fudis-grid-item", usesInheritance: true, ngImport: i0, template: "<ng-content></ng-content>\n", styles: [".fudis-grid-item{box-sizing:border-box;margin:0;padding:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GridItemComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-grid-item', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>\n", styles: [".fudis-grid-item{box-sizing:border-box;margin:0;padding:0}\n"] }]
}] });
/**
* Visually separates content sections.
*
* Use this component to visually indicate thematic breaks.
*/
class HorizontalRuleComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HorizontalRuleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: HorizontalRuleComponent, isStandalone: true, selector: "fudis-hr", ngImport: i0, template: "<hr class=\"fudis-hr\" aria-hidden=\"true\" />\n", changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HorizontalRuleComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-hr', changeDetection: ChangeDetectionStrategy.OnPush, template: "<hr class=\"fudis-hr\" aria-hidden=\"true\" />\n" }]
}] });
/**
* Indicates a loading or processing state.
*
* Use this component to inform users of ongoing activity or process.
*/
class LoadingSpinnerComponent {
constructor(_translationService) {
this._translationService = _translationService;
/**
* Size variant. Variant 'sm' for smaller page sections and 'lg' for full page.
*/
this.variant = 'sm';
/**
* For variant 'lg' and better screen reader experience, instead of using `@if` for displaying
* component, set this property true when loading is in progress and false, when loading is not in
* progress. This will trigger screen reader `statusMessage` properties accordingly.
*/
this.visible = true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LoadingSpinnerComponent, deps: [{ token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: LoadingSpinnerComponent, isStandalone: true, selector: "fudis-loading-spinner", inputs: { label: "label", variant: "variant", statusMessage: "statusMessage", visible: "visible" }, ngImport: i0, template: "@let translations = _translationService.getTranslations()();\n\n<div class=\"fudis-loading-spinner\">\n @if (variant === \"lg\") {\n <p class=\"fudis-loading-spinner__status fudis-visually-hidden\" role=\"status\">{{\n statusMessage ||\n (visible\n ? translations.LOADING_SPINNER.PAGE_LOADING\n : translations.LOADING_SPINNER.PAGE_LOAD_FINISHED)\n }}</p>\n }\n @if (visible) {\n <div class=\"fudis-loading-spinner__ui-content\">\n <svg\n class=\"fudis-loading-spinner__svg fudis-loading-spinner__variant__{{ variant }}\"\n aria-hidden=\"true\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M15.2256 0.441641C12.7017 -0.262704 10.0165 -0.121557 7.58041 0.843527C5.14424 1.80861 3.09092 3.54458 1.73407 5.78628C0.377218 8.02798 -0.208579 10.6522 0.066151 13.2581C0.34088 15.864 1.46103 18.3084 3.25553 20.2179C5.05002 22.1274 7.4202 23.397 10.004 23.8328C12.5879 24.2687 15.2434 23.8468 17.565 22.6316L16.1133 19.8581C14.3974 20.7563 12.4346 21.0681 10.5247 20.746C8.61493 20.4239 6.86307 19.4855 5.5367 18.0741C4.21034 16.6628 3.3824 14.856 3.17934 12.9299C2.97627 11.0038 3.40925 9.06416 4.41214 7.40725C5.41504 5.75033 6.93271 4.46723 8.73335 3.75391C10.534 3.04059 12.5186 2.93626 14.3841 3.45686C16.2497 3.97747 17.8935 5.09438 19.0645 6.63702C20.2356 8.17965 20.8696 10.0632 20.8696 12H24C24 9.37963 23.1424 6.83129 21.5579 4.7442C19.9735 2.65711 17.7496 1.14599 15.2256 0.441641Z\"\n />\n </svg>\n <fudis-body-text\n [align]=\"'center'\"\n [variant]=\"variant === 'sm' ? 'md-regular' : 'lg-regular'\"\n >{{ label || translations.LOADING_SPINNER.VISIBLE_LABEL }}</fudis-body-text\n >\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: BodyTextComponent, selector: "fudis-body-text", inputs: ["variant", "align", "lang"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LoadingSpinnerComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-loading-spinner', imports: [BodyTextComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let translations = _translationService.getTranslations()();\n\n<div class=\"fudis-loading-spinner\">\n @if (variant === \"lg\") {\n <p class=\"fudis-loading-spinner__status fudis-visually-hidden\" role=\"status\">{{\n statusMessage ||\n (visible\n ? translations.LOADING_SPINNER.PAGE_LOADING\n : translations.LOADING_SPINNER.PAGE_LOAD_FINISHED)\n }}</p>\n }\n @if (visible) {\n <div class=\"fudis-loading-spinner__ui-content\">\n <svg\n class=\"fudis-loading-spinner__svg fudis-loading-spinner__variant__{{ variant }}\"\n aria-hidden=\"true\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M15.2256 0.441641C12.7017 -0.262704 10.0165 -0.121557 7.58041 0.843527C5.14424 1.80861 3.09092 3.54458 1.73407 5.78628C0.377218 8.02798 -0.208579 10.6522 0.066151 13.2581C0.34088 15.864 1.46103 18.3084 3.25553 20.2179C5.05002 22.1274 7.4202 23.397 10.004 23.8328C12.5879 24.2687 15.2434 23.8468 17.565 22.6316L16.1133 19.8581C14.3974 20.7563 12.4346 21.0681 10.5247 20.746C8.61493 20.4239 6.86307 19.4855 5.5367 18.0741C4.21034 16.6628 3.3824 14.856 3.17934 12.9299C2.97627 11.0038 3.40925 9.06416 4.41214 7.40725C5.41504 5.75033 6.93271 4.46723 8.73335 3.75391C10.534 3.04059 12.5186 2.93626 14.3841 3.45686C16.2497 3.97747 17.8935 5.09438 19.0645 6.63702C20.2356 8.17965 20.8696 10.0632 20.8696 12H24C24 9.37963 23.1424 6.83129 21.5579 4.7442C19.9735 2.65711 17.7496 1.14599 15.2256 0.441641Z\"\n />\n </svg>\n <fudis-body-text\n [align]=\"'center'\"\n [variant]=\"variant === 'sm' ? 'md-regular' : 'lg-regular'\"\n >{{ label || translations.LOADING_SPINNER.VISIBLE_LABEL }}</fudis-body-text\n >\n </div>\n }\n</div>\n" }]
}], ctorParameters: () => [{ type: FudisTranslationService }], propDecorators: { label: [{
type: Input
}], variant: [{
type: Input
}], statusMessage: [{
type: Input
}], visible: [{
type: Input
}] } });
/**
* Utility function to test, if two objects have the exactly same properties
*
* @param x First object
* @param y Second object
* @returns Boolean
*/
const areObjectsDeepEquals = (x, y) => {
const ok = Object.keys;
const tx = typeof x;
const ty = typeof y;
return x && y && tx === 'object' && tx === ty
? ok(x).length === ok(y).length && ok(x).every((key) => areObjectsDeepEquals(x[key], y[key]))
: x === y;
};
/**
* Function to loop array of options and join their labels to a single string
*
* @param values Array of selected Options
* @returns
*/
const joinInputValues = (values) => {
const label = [];
values.forEach((item) => {
const labelToPush = item.label.includes(',') ? `'${item.label}'` : item.label;
label.push(labelToPush);
});
const joinedValues = label.join(', ');
return joinedValues;
};
/**
* Add or remove a value from current list and returned updated one
*
* @param currentList
* @param valueToUpdate
* @param visible
* @returns Updated List
*/
const setVisibleOptionsList = (currentList, valueToUpdate, visible) => {
const listToReturn = [...currentList];
const valueExists = listToReturn.includes(valueToUpdate);
if (visible && !valueExists) {
listToReturn.push(valueToUpdate);
}
else if (valueExists && !visible) {
const index = listToReturn.indexOf(valueToUpdate);
listToReturn.splice(index, 1);
}
return listToReturn;
};
// TODO: Write tests
/**
* Marks the container for select options.
*
* Use this directive to tag the template which wraps (multi)select options and enables lazy loading
* of the options.
*/
class SelectOptionsDirective {
constructor(templateRef) {
this.templateRef = templateRef;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectOptionsDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SelectOptionsDirective, isStandalone: true, selector: "[fudisSelectOptions]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectOptionsDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisSelectOptions]' }]
}], ctorParameters: () => [{ type: i0.TemplateRef }] });
class SelectBaseDirective extends ControlComponentBaseDirective {
constructor(_document, _dialogService, _focusService, _idService) {
super(_idService, _focusService);
this._document = _document;
this._dialogService = _dialogService;
/**
* Set dropdown size (should follow the given input element size)
*/
this.size = 'lg';
/**
* Select variant: 'dropdown' | 'autocompleteDropdown' | 'autocompleteType'.
*
* - 'dropdown': default, normal select dropdown
* - 'autocompleteDropdown': dropdown with autocomplete input field
* - 'autocompleteType': autocomplete but user must type 3 letters before any results are displayed
*/
this.variant = 'dropdown';
/**
* Enable / disable button, which clears user selection when there is a selected value
*/
this.selectionClearButton = true;
/**
* By default Autocomplete filters options loaded to the DOM based on user input. When this is set
* to 'false', filtering is disabled and all options available in DOM are displayed regardless of
* user's input. Disabling can be useful, if application wants to implement their own filtering
* logic. E. g. get user's input, run a backend search and create list of options for the Select.
*/
this.autocompleteFilter = true;
/**
* By default, Autocomplete variant will display "No results found" text when there are 0 options
* matching. When combined with 'autocompleteFilter' false, application can set their own
* 'Fetching options...' etc. text while their own filtering is in progress.
*/
this.autocompleteNoResultsText = null;
/**
* Value output event on selection change
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.selectionUpdate = new EventEmitter();
/**
* Output for number of visible options after filtering results
*/
this.visibleOptionsUpdate = new EventEmitter();
/**
* Output for filter text after filtering results
*/
this.filterTextUpdate = new EventEmitter();
/**
* CSS selector for querying focus states
*/
this.focusSelector = '.fudis-select-option__focusable';
/**
* For setting dropdown open / closed
*/
this._dropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "_dropdownOpen" }] : /* istanbul ignore next */ []));
/**
* Signal to Select & MultiselectOption for listening autocomplete filter text changes
*/
this._autocompleteFilterText = signal('', ...(ngDevMode ? [{ debugName: "_autocompleteFilterText" }] : /* istanbul ignore next */ []));
/**
* Visible results length to be passed on SelectDropdown. This value determines the results shown
* in autocomplete variants helptext.
*/
this._resultsLength = signal(0, ...(ngDevMode ? [{ debugName: "_resultsLength" }] : /* istanbul ignore next */ []));
/**
* Lazy loading check for expanding content, unless component control gets values from
* application, then set to true automatically, so that comparing available options match given
* control value.
*/
this._optionsLoadedOnce = signal(false, ...(ngDevMode ? [{ debugName: "_optionsLoadedOnce" }] : /* istanbul ignore next */ []));
/**
* Used when filtering autocomplete results to check if 'No results found' text is visible
*/
this._visibleOptions = [];
/**
* Status of input focus
*/
this._inputFocused = false;
/**
* Used to handle exceptions when mouse click event fires before / after focus event or user has
* clicked autocomplete clear button
*/
this._preventDropdownReopen = false;
/**
* Internal signals for template binding and managing the state of the form control.
*/
this._touched = signal(false, ...(ngDevMode ? [{ debugName: "_touched" }] : /* istanbul ignore next */ []));
this._invalid = signal(false, ...(ngDevMode ? [{ debugName: "_invalid" }] : /* istanbul ignore next */ []));
this._disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
this._enabled = signal(true, ...(ngDevMode ? [{ debugName: "_enabled" }] : /* istanbul ignore next */ []));
this._selectedLabel = signal(null, ...(ngDevMode ? [{ debugName: "_selectedLabel" }] : /* istanbul ignore next */ []));
/**
* Reactive reference to the current control instance.
*
* Used by child option components to rebind their subscription if the parent `control` input is
* replaced with a new FormControl instance.
*/
this._controlRef = signal(null, ...(ngDevMode ? [{ debugName: "_controlRef" }] : /* istanbul ignore next */ []));
/**
* Focus try counter
*/
this._focusTryCounter = 0;
/**
* Array to store visible options while options are loading.
*/
this._visibleOptionsTemp = [];
/**
* Currently focused option
*/
this._focusedOption = null;
/**
* If clear button is focused
*/
this._clearButtonFocused = false;
/**
* If click event's target is Select's input field
*/
this._mouseUpOnInput = false;
/**
* Used to not update visible options to HTML template before some delay has passed in case new
* options are still loading
*/
this._optionLoadInterval = null;
/**
* If click event originated from Icon used inside input field
*/
this._clickFromIcon = false;
/**
* Keyboard button pressed down
*/
this._keyDown = null;
/**
* Used to pass info, that Clear Button was clicked
*/
this._clearButtonClickTrigger = signal(false, ...(ngDevMode ? [{ debugName: "_clearButtonClickTrigger" }] : /* istanbul ignore next */ []));
this._updateValueAndValidityTrigger.pipe(takeUntilDestroyed()).subscribe(() => {
if (this.control) {
this._required.next(FudisValidatorUtilities.required(this.control));
}
});
/**
* This is for detecting the input field size changes so the dropdown width and height can be
* adjusted.
*/
this._resizeObserver = new ResizeObserver(() => {
this._calculateDropdownPosition();
});
/**
* Observable for viewport height and orientation change, will cause dropdown to close.
*/
fromEvent(window, 'resize')
.pipe(takeUntilDestroyed())
.subscribe(() => {
if (window.innerHeight !== this._viewportLastHeight && this._dropdownOpen()) {
this._viewportLastHeight = window.innerHeight;
this.closeDropdown(false, true);
}
});
}
_calculateDropdownPosition() {
if (this._dropdownOpen()) {
const inputElement = this._inputRef?.nativeElement;
const inputRect = inputElement?.getBoundingClientRect();
const dropDownElement = this._selectRef?.nativeElement?.querySelector('.fudis-select-dropdown');
if (dropDownElement && inputRect) {
dropDownElement.style.top = `${inputRect.bottom}px`;
dropDownElement.style.left = `${inputRect.left}px`;
dropDownElement.style.maxWidth = `${inputRect.width}px`;
/**
* If window has less available space than the default height of the dropdown menu, snap the
* dropdown menu to the bottom of the window.
*/
const cssDefaultMaxHeight = 256;
const availableSpace = window.innerHeight - inputRect.bottom - 6;
const finalMaxHeight = Math.min(cssDefaultMaxHeight, availableSpace);
dropDownElement.style.maxHeight = `${finalMaxHeight}px`;
}
}
}
_setupScrollListener() {
this._scrollListener = () => {
this._calculateDropdownPosition();
};
document.addEventListener('scroll', this._scrollListener, true);
}
_removeScrollListener() {
if (this._scrollListener)
document.removeEventListener('scroll', this._scrollListener, true);
}
_unsubscribeDropdownSubscribtions() {
this._resizeObserver.disconnect();
this._removeScrollListener();
}
ngOnChanges(changes) {
if (changes.control?.currentValue !== changes.control?.previousValue) {
this._controlRef.set(this.control);
this._syncControlState();
this._updateValueAndValidityTrigger.next();
this._updateComponentStateFromControlValue();
if (this.control.value) {
this._optionsLoadedOnce.set(true);
}
this._subscription?.unsubscribe();
this._subscription = this.control.events
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => {
this._syncControlState();
this._updateValueAndValidityTrigger.next();
if (this.control.value) {
this._optionsLoadedOnce.set(true);
}
this._updateComponentStateFromControlValue();
});
}
if (changes.variant?.currentValue !== changes.variant?.previousValue &&
changes.variant?.currentValue === 'dropdown' &&
!changes.variant.firstChange) {
this.setAutocompleteFilterText('');
}
if (changes.autocompleteFilter?.currentValue !== changes.autocompleteFilter?.previousValue &&
!changes.autocompleteFilter?.currentValue) {
this._autocompleteFilterText.set(this._autocompleteFilterText());
}
}
ngOnDestroy() {
this._unsubscribeDropdownSubscribtions();
}
/**
* @returns Signal value of autocomplete filter text
*/
getAutocompleteFilterText() {
return this._autocompleteFilterText.asReadonly();
}
/**
* Exposes the current control reference as a readonly signal.
*
* Option components use this to follow parent control instance changes and resubscribe to the
* latest control events.
*/
getControlRef() {
return this._controlRef.asReadonly();
}
/**
* Open dropdown
*/
openDropdown() {
if (!this.control.disabled && !this.disabled) {
this._syncControlState();
this._optionsLoadedOnce.set(true);
this._dropdownOpen.set(true); // TODO FIXME: When using autocompleteType variant, this should be called only if filter text is applied
this._unsubscribeDropdownSubscribtions();
this._resizeObserver.observe(document?.body);
this._setupScrollListener();
}
}
/**
* Close dropdown
*
* @param focusToInput: When dropdown closes, focus or not to the input
* @param preventDropdownReopen: For cases, when closing command comes from outside eg. clicking
* an option in the dropdownlist. There's no need to reopen the dropdown when focusing back to
* the input, which usually triggers opening the dropdown.
*/
closeDropdown(focusToInput = true, preventDropdownReopen = false) {
this._dropdownOpen.set(false);
this._unsubscribeDropdownSubscribtions();
this._preventDropdownReopen = preventDropdownReopen;
if (focusToInput) {
this._focusToSelectInput();
}
}
/**
* Each option sends information to parent if they are visible or not
*
* @param value Option value
* @param visible Is this option visible or not
*/
setOptionVisibility(value, visible) {
this._latestVisibleOption = value;
this._visibleOptionsTemp = setVisibleOptionsList(this._visibleOptionsTemp, value, visible);
if (!this._optionLoadInterval) {
this._optionsLoadDelay().then((resolve) => {
if (resolve) {
this._visibleOptions = this._visibleOptionsTemp;
this._resultsLength.set(this._visibleOptions.length);
this.visibleOptionsUpdate.emit(this._visibleOptions.length);
}
});
}
}
/**
* Add or remove currently focused option. Called from SelectOptionBase.
*/
setFocusedOption(id, type) {
if (type === 'add') {
this._focusedOption = id;
}
else {
this._focusedOption = null;
}
}
/**
* Promise which determines, if some of the components used in this Select has focus or not.
*
* @param event FocusEvent
* @returns
*/
componentFocused(event) {
return new Promise((resolve) => {
let counter = 0;
const nextTarget = event?.relatedTarget;
const focusCheckInterval = setInterval(() => {
const focused = !!this._selectRef.nativeElement.contains(this._document.activeElement) ||
!!this._selectRef.nativeElement.contains(nextTarget);
// If focus has moved another element inside Select
if (focused || this._mouseDownInsideComponent) {
clearInterval(focusCheckInterval);
this._mouseDownInsideComponent = false;
resolve(true);
// If focus target is null
}
else if (!nextTarget) {
clearInterval(focusCheckInterval);
resolve(false);
// Increase counter, and try again. This is needed usually with click events as between previous element blur and next element focus click event is "somewhere else"
}
else if (counter <= 100) {
counter = counter + 50;
}
else {
// Else resolve boolean check after two tries, if any relevant element is focused
clearInterval(focusCheckInterval);
resolve(!!this._focusedOption || this._inputFocused || this._clearButtonFocused);
}
}, 50);
});
}
/**
* When Clear button is clicked
*/
_clearButtonClick() {
if (!this.control.disabled && !this.disabled) {
this.control.markAsTouched();
this._clearButtonClickTrigger.set(true);
this._setControlNull();
this._focusToSelectInput();
}
}
/**
* Set control value to null Control value should reset even when user input does not match any
* option value (i.e. control value is null), so that dropdown shows options correctly
*/
_setControlNull() {
if (this.control.value) {
this.selectionUpdate.emit(null);
this.control.patchValue(null);
this.setAutocompleteFilterText('');
}
else {
this.control.patchValue(null);
this.setAutocompleteFilterText('');
}
}
/**
* To handle input focus
*/
_selectInputFocus(event) {
this._inputFocused = true;
if (this._preventDropdownReopen) {
this._preventDropdownReopen = false;
this.onFocus(event);
return;
}
const openDropdown = this.variant === 'dropdown' ||
this.variant === 'autocompleteDropdown' ||
(this.variant === 'autocompleteType' && this._autocompleteFilterText() !== '');
if (!this._dropdownOpen() && openDropdown && !this._mouseDownInsideComponent) {
this.openDropdown();
}
else if (this._clickFromIcon) {
if (this._dropdownOpen()) {
this.closeDropdown();
}
else {
this.openDropdown();
}
}
this.onFocus(event);
}
/**
* To handle input field blur events
*
* @param event FocusEvent
*/
_inputBlur(event) {
this._inputFocused = false;
this.componentFocused(event).then((value) => {
if (!value) {
this.closeDropdown(false);
this.control.markAsTouched();
}
});
}
/**
* To handle click events for input
*/
_clickInput() {
this._preventDropdownReopen = true;
if (this._inputFocused || this._mouseUpOnInput) {
if (this.variant === 'dropdown') {
this._toggleDropdown();
}
else {
this.openDropdown();
}
}
this._focusToSelectInput();
}
/**
* Register pressed key inside input field. Used to check that both key down and key up originated
* from same source.
*/
_inputKeyDown(event) {
this._keyDown = event.key;
// Prevent scrolling
if (event.key === 'ArrowDown') {
event.preventDefault();
}
// Prevent keydown event if Enter is pressed inside multiselect input
if (event.key === 'Enter') {
event.preventDefault();
}
}
/**
* Handle keypress for dropdown select
*
* @param event KeyboardEvent
* @param focusSelector CSS selector to focus to on ArrowDown event
*/
_inputKeyUp(event) {
const { key } = event;
if (key === this._keyDown) {
switch (key) {
case ' ':
if (this.variant === 'dropdown') {
event.preventDefault();
this._toggleDropdown();
}
break;
case 'Enter':
event.preventDefault();
if (this._visibleOptions.length === 1) {
this._focusToFirstOption(true);
}
else {
this._toggleDropdown();
}
break;
case 'ArrowDown':
event.preventDefault();
if (!this._dropdownOpen()) {
this._toggleDropdown();
}
if (this._inputFocused) {
this._focusToFirstOption();
}
break;
case 'Tab':
break;
default:
event.preventDefault();
}
}
this._keyDown = null;
}
/**
* Generate html id for parent FudisSelect
*/
_setParentId(type) {
if (this.id && type === 'select') {
this._idService.addNewGrandParentId('select', this.id);
}
else if (type === 'select') {
this.id = this._idService.getNewGrandParentId('select');
}
else if (this.id && type === 'multiselect') {
this._idService.addNewGrandParentId('multiselect', this.id);
}
else {
this.id = this._idService.getNewGrandParentId('multiselect');
}
}
/**
* Toggle dropdown
*/
_toggleDropdown() {
if (this._dropdownOpen()) {
this.closeDropdown();
}
else {
this.openDropdown();
}
}
/**
* Resolve a promise after delay if there hasn't been new options
*
* @returns Boolean
*/
_optionsLoadDelay() {
let tempLatestOptions;
return new Promise((resolve) => {
this._optionLoadInterval = setInterval(() => {
if (tempLatestOptions === this._latestVisibleOption) {
if (this._optionLoadInterval) {
clearInterval(this._optionLoadInterval);
this._optionLoadInterval = null;
}
resolve(true);
}
else {
tempLatestOptions = this._latestVisibleOption;
}
}, 50);
});
}
/**
* Copy FormControl states into local signals
*/
_syncControlState() {
if (!this.control)
return;
this._touched.set(this.control.touched);
this._invalid.set(this.control.invalid);
this._disabled.set(this.control.disabled);
this._enabled.set(this.control.enabled);
this._selectedLabel.set(this.control.value?.label ?? null);
}
/**
* Set Clear button's focus state to false
*/
_setClearButtonFocusFalse() {
this._clearButtonFocused = false;
}
/**
* Set focus state of Clear Button and determine if Dropdown should be closed when this function
* is called
*
* @param event FocusEvent
* @param state
*/
_setClearButtonFocusState(event, state) {
this._clearButtonFocused = state;
if (!state) {
this.componentFocused(event).then((value) => {
if (!value) {
this.closeDropdown(false);
}
});
}
}
/**
* Update input filter text
*
* @param text String to set as filter text
* @param nullCheck True by default, check if control should be set as null
*/
setAutocompleteFilterText(text, nullCheck = true) {
if (this._autocompleteFilterText() !== text) {
this._autocompleteFilterText.set(text);
this.filterTextUpdate.emit(text);
}
if (nullCheck) {
this._checkIfAutocompleteValueNull(text);
}
}
/**
* Checks if currently typed filter text is not same as control label value
*
* @param text Filter text value emitted from autocomplete
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_checkIfAutocompleteValueNull(text) { }
/**
* To focus on first option when dropdown opens
*
* @param cssfocusSelector CSS class to focus to
*/
_focusToFirstOption(clickFirstOption) {
const firstOption = this._dropdownRef?.dropdownElement.nativeElement.querySelector(this.focusSelector);
if (firstOption) {
firstOption.focus();
if (clickFirstOption) {
firstOption.click();
}
if (this._document.activeElement !== firstOption) {
setTimeout(() => {
firstOption.focus();
if (clickFirstOption) {
firstOption.click();
}
}, 0);
}
}
else if (this._focusTryCounter < 20) {
setTimeout(() => {
this._focusTryCounter += 1;
this._focusToFirstOption();
}, 100);
}
}
/**
* When blurring away from Dropdown, check if this Select has focus
*/
_dropdownBlur(event) {
this.componentFocused(event).then((value) => {
if (!value) {
this.closeDropdown(false);
}
});
}
/**
* Browser focus logic differs. E. g. Firefox tries to focus to Dropdown menu wrapper. This
* function determines that if it should focus to first option or back to input field.
*/
_dropdownFocus(event) {
const focusFromInputOrClearButton = event.relatedTarget === this._inputRef?.nativeElement ||
this._selectIconsRef.nativeElement.contains(event.relatedTarget);
if (focusFromInputOrClearButton) {
this._focusToFirstOption();
}
else {
this._focusToSelectInput();
}
}
/**
* Focus to input field
*/
_focusToSelectInput() {
this._inputRef?.nativeElement.focus();
}
/**
* Function declaration overridden and implemented by Select and Multiselect
*/
_updateComponentStateFromControlValue() { }
/**
* When pressing keyboard Esc, focus to Select input, set flag that dropdown was closed with
* Escape key and close dropdown
*
* @param event
*/
_handleEscapePress(event) {
if (event.key === 'Escape' && this._dropdownOpen()) {
event.preventDefault();
this._dialogService.dropdownClosedWithEscape();
this.closeDropdown(true, true);
}
}
/**
* When user clicks, set status whether click is inside or outside the Select element
*
* @param targetElement
*/
_handleWindowClick(event) {
this._mouseDownInsideComponent = false;
const targetElement = event.target;
// Type guard
if (targetElement instanceof Element) {
if (this._dropdownOpen() && !this._selectRef.nativeElement.contains(targetElement)) {
this.closeDropdown(false);
}
}
}
_handleMouseDown() {
this._mouseDownInsideComponent = true;
}
_handleMouseUp(event) {
const targetElement = event.target;
// Type guard
if (!(targetElement instanceof Element))
return;
this._clickFromIcon =
this._selectRef.nativeElement.contains(targetElement) &&
!!targetElement.closest('.fudis-select-icons__container');
this._mouseUpOnInput =
targetElement &&
(!!this._inputRef?.nativeElement.contains(targetElement) || this._clickFromIcon);
if (this._clickFromIcon) {
if (!this.control.disabled) {
this._focusToSelectInput();
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectBaseDirective, deps: [{ token: DOCUMENT }, { token: FudisDialogService }, { token: FudisFocusService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SelectBaseDirective, isStandalone: true, selector: "[fudisSelectBase]", inputs: { size: "size", placeholder: "placeholder", variant: "variant", selectionClearButton: "selectionClearButton", autocompleteFilter: "autocompleteFilter", autocompleteHelpText: "autocompleteHelpText", autocompleteNoResultsText: "autocompleteNoResultsText" }, outputs: { selectionUpdate: "selectionUpdate", visibleOptionsUpdate: "visibleOptionsUpdate", filterTextUpdate: "filterTextUpdate" }, host: { listeners: { "window:keydown": "_handleEscapePress($event)", "document:mouseup": "_handleWindowClick($event)", "mousedown": "_handleMouseDown()", "mouseup": "_handleMouseUp($event)" } }, queries: [{ propertyName: "_selectOptionsDirective", first: true, predicate: SelectOptionsDirective, descendants: true }], viewQueries: [{ propertyName: "_dropdownRef", first: true, predicate: ["dropdownRef"], descendants: true }, { propertyName: "_selectIconsRef", first: true, predicate: ["selectIconsRef"], descendants: true }, { propertyName: "_selectRef", first: true, predicate: ["selectRef"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectBaseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisSelectBase]' }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: FudisDialogService }, { type: FudisFocusService }, { type: FudisIdService }], propDecorators: { _dropdownRef: [{
type: ViewChild,
args: ['dropdownRef']
}], _selectIconsRef: [{
type: ViewChild,
args: ['selectIconsRef']
}], _selectOptionsDirective: [{
type: ContentChild,
args: [SelectOptionsDirective]
}], _selectRef: [{
type: ViewChild,
args: ['selectRef']
}], size: [{
type: Input
}], placeholder: [{
type: Input
}], variant: [{
type: Input
}], selectionClearButton: [{
type: Input
}], autocompleteFilter: [{
type: Input
}], autocompleteHelpText: [{
type: Input
}], autocompleteNoResultsText: [{
type: Input
}], selectionUpdate: [{
type: Output
}], visibleOptionsUpdate: [{
type: Output
}], filterTextUpdate: [{
type: Output
}], _handleEscapePress: [{
type: HostListener,
args: ['window:keydown', ['$event']]
}], _handleWindowClick: [{
type: HostListener,
args: ['document:mouseup', ['$event']]
}], _handleMouseDown: [{
type: HostListener,
args: ['mousedown']
}], _handleMouseUp: [{
type: HostListener,
args: ['mouseup', ['$event']]
}] } });
/* eslint-disable @typescript-eslint/no-explicit-any */
class SelectBaseControlValueAccessorDirective {
constructor() {
this.onChanged = () => { };
this.onTouched = () => { };
this.onValidate = () => { };
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
writeValue(value) { }
registerOnChange(fn) {
this.onChanged = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
validate(control) {
if (control.invalid) {
return control.errors;
}
else {
return null;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectBaseControlValueAccessorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SelectBaseControlValueAccessorDirective, isStandalone: false, selector: "[fudisSelectBaseControlValueAccessor]", providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectBaseControlValueAccessorDirective,
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: SelectBaseControlValueAccessorDirective,
multi: true,
},
], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectBaseControlValueAccessorDirective, decorators: [{
type: Directive,
args: [{
selector: '[fudisSelectBaseControlValueAccessor]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectBaseControlValueAccessorDirective,
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: SelectBaseControlValueAccessorDirective,
multi: true,
},
],
standalone: false,
}]
}], ctorParameters: () => [] });
class SelectControlValueAccessorDirective extends SelectBaseControlValueAccessorDirective {
constructor(_elementRef, _renderer) {
super();
this._elementRef = _elementRef;
this._renderer = _renderer;
}
ngOnChanges(changes) {
if (changes.filterText?.currentValue !== changes.filterText?.previousValue) {
this.writeValue();
}
}
writeValue(value) {
const valueToSet = value?.label || null;
if (valueToSet) {
this._renderer.setAttribute(this._elementRef.nativeElement, 'value', valueToSet);
if (this._elementRef.nativeElement.value !== valueToSet) {
this._elementRef.nativeElement.value = valueToSet;
}
}
else if (this.filterText) {
this._renderer.setAttribute(this._elementRef.nativeElement, 'value', this.filterText);
if (this._elementRef.nativeElement.value !== this.filterText) {
this._elementRef.nativeElement.value = this.filterText;
}
}
else {
this._renderer.removeAttribute(this._elementRef.nativeElement, 'value');
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectControlValueAccessorDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SelectControlValueAccessorDirective, isStandalone: true, selector: "[fudisSelectControlValueAccessor]", inputs: { filterText: "filterText" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectControlValueAccessorDirective,
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: SelectControlValueAccessorDirective,
multi: true,
},
], usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectControlValueAccessorDirective, decorators: [{
type: Directive,
args: [{
selector: '[fudisSelectControlValueAccessor]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectControlValueAccessorDirective,
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: SelectControlValueAccessorDirective,
multi: true,
},
],
}]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { filterText: [{
type: Input
}] } });
class MultiselectControlValueAccessorDirective extends SelectBaseControlValueAccessorDirective {
constructor(_document, _elementRef, _renderer) {
super();
this._document = _document;
this._elementRef = _elementRef;
this._renderer = _renderer;
this.enableAutocomplete = false;
this.handleSortedSelectedOptions = new EventEmitter();
/**
* When selecting / deselecting options, variable for storing them in the order of their id's
* (usually the DOM order)
*/
this._selectedOptions = null;
}
ngAfterViewInit() {
this.writeValue(this._selectedOptions);
}
writeValue(value) {
this._selectedOptions = value;
this._setVisibleLabel(value);
}
_setVisibleLabel(value) {
const dropdown = this._document.getElementById(`${this.id}-dropdown`);
if (value) {
const sortedSelectedOptions = dropdown
? [...value].sort(this._sortSelectedOptionsDOMOrder(dropdown, this.id))
: [...value];
this.handleSortedSelectedOptions.emit(sortedSelectedOptions);
if (!this.enableAutocomplete) {
const labelToSet = joinInputValues(sortedSelectedOptions);
this._renderer.setAttribute(this._elementRef.nativeElement, 'value', labelToSet);
}
}
else {
this._renderer.removeAttribute(this._elementRef.nativeElement, 'value');
this.handleSortedSelectedOptions.emit(null);
}
}
/**
* Sort selected options the same order they appear in the DOM
*/
_sortSelectedOptionsDOMOrder(dropdown, id) {
return function (a, b) {
const aElementId = FudisIdService.createSelectOptionId(id, a.label);
const bElementId = FudisIdService.createSelectOptionId(id, b.label);
if (aElementId === bElementId) {
return 0;
}
if (dropdown) {
const firstEl = dropdown.querySelector(`#${aElementId}`);
const secondEl = dropdown.querySelector(`#${bElementId}`);
if (firstEl && secondEl) {
const position = firstEl.compareDocumentPosition(secondEl);
if (position & Node.DOCUMENT_POSITION_FOLLOWING ||
position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
return -1;
}
else if (position & Node.DOCUMENT_POSITION_PRECEDING ||
position & Node.DOCUMENT_POSITION_CONTAINS) {
return 1;
}
}
}
return 0;
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectControlValueAccessorDirective, deps: [{ token: DOCUMENT }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: MultiselectControlValueAccessorDirective, isStandalone: true, selector: "[fudisMultiselectControlValueAccessor]", inputs: { id: "id", enableAutocomplete: "enableAutocomplete" }, outputs: { handleSortedSelectedOptions: "handleSortedSelectedOptions" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: MultiselectControlValueAccessorDirective,
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: MultiselectControlValueAccessorDirective,
multi: true,
},
], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectControlValueAccessorDirective, decorators: [{
type: Directive,
args: [{
selector: '[fudisMultiselectControlValueAccessor]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: MultiselectControlValueAccessorDirective,
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: MultiselectControlValueAccessorDirective,
multi: true,
},
],
}]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { id: [{
type: Input
}], enableAutocomplete: [{
type: Input
}], handleSortedSelectedOptions: [{
type: Output
}] } });
class SelectAutocompleteBaseDirective {
constructor(_elementRef) {
this._elementRef = _elementRef;
/**
* To enable autocomplete
*/
this.enableAutocomplete = false;
/**
* Output event for updating parent's filter text signal
*/
this.handleFilterTextUpdate = new EventEmitter();
/**
* Output event for opening parent dropdown
*/
this.handleDropdownOpen = new EventEmitter();
/**
* Output event for closing parent dropdown
*/
this.handleDropdownClose = new EventEmitter();
/**
* Output event for closing parent dropdown
*/
this.handleClearButtonReset = new EventEmitter();
/**
* Output event for input focus
*/
this.handleFocus = new EventEmitter();
/**
* Output event for input blur
*/
this.handleBlur = new EventEmitter();
/**
* If Focus came from related clear button
*/
this._focusFromClearButton = false;
/**
* Currently typed input text
*/
this._inputText = '';
/**
* If input is focused or not
*/
this._focused = false;
}
_handleFocus(event) {
this._focused = true;
if (event.relatedTarget?.getAttribute('id') === `${this.id}-clear-button`) {
this._focusFromClearButton = true;
}
this.handleFocus.emit({ event: event, focusFromClearButton: this._focusFromClearButton });
}
_handleBlur(event) {
this._focused = false;
this.handleBlur.emit(event);
}
_handleKeyDown(event) {
if (event.target) {
this._keyDown = event.key;
}
}
_handleKeyUp(event) {
if (this.enableAutocomplete) {
if (this._keyDown) {
const newValue = event.target.value;
if (newValue !== this._inputText) {
this._inputText = newValue;
if (newValue.length >= this.typeThreshold) {
this.handleFilterTextUpdate.emit(newValue);
this.handleDropdownOpen.emit();
}
else {
this.handleDropdownClose.emit();
this.handleFilterTextUpdate.emit('');
}
}
}
else if (this._focused &&
this._focusFromClearButton &&
(event.key === 'Enter' || event.key === ' ')) {
this._elementRef.nativeElement.value = '';
this.handleFilterTextUpdate.emit('');
if (this.typeThreshold === 0) {
this.handleDropdownOpen.emit();
}
else {
this.handleDropdownClose.emit();
}
}
this._focusFromClearButton = false;
this._keyDown = null;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectAutocompleteBaseDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SelectAutocompleteBaseDirective, isStandalone: true, selector: "[fudisSelectAutocompleteBase]", inputs: { enableAutocomplete: "enableAutocomplete", typeThreshold: "typeThreshold", id: "id", clearButtonClick: "clearButtonClick" }, outputs: { handleFilterTextUpdate: "handleFilterTextUpdate", handleDropdownOpen: "handleDropdownOpen", handleDropdownClose: "handleDropdownClose", handleClearButtonReset: "handleClearButtonReset", handleFocus: "handleFocus", handleBlur: "handleBlur" }, host: { listeners: { "focus": "_handleFocus($event)", "blur": "_handleBlur($event)", "keydown": "_handleKeyDown($event)", "keyup": "_handleKeyUp($event)" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectAutocompleteBaseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisSelectAutocompleteBase]' }]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { enableAutocomplete: [{
type: Input
}], typeThreshold: [{
type: Input,
args: [{ required: true }]
}], id: [{
type: Input
}], clearButtonClick: [{
type: Input
}], handleFilterTextUpdate: [{
type: Output
}], handleDropdownOpen: [{
type: Output
}], handleDropdownClose: [{
type: Output
}], handleClearButtonReset: [{
type: Output
}], handleFocus: [{
type: Output
}], handleBlur: [{
type: Output
}], _handleFocus: [{
type: HostListener,
args: ['focus', ['$event']]
}], _handleBlur: [{
type: HostListener,
args: ['blur', ['$event']]
}], _handleKeyDown: [{
type: HostListener,
args: ['keydown', ['$event']]
}], _handleKeyUp: [{
type: HostListener,
args: ['keyup', ['$event']]
}] } });
class SelectAutocompleteDirective extends SelectAutocompleteBaseDirective {
constructor(_elementRef) {
super(_elementRef);
}
ngOnChanges(changes) {
if (this.enableAutocomplete) {
const selectedLabel = changes.selectedLabel?.currentValue;
const clearButtonClick = changes.clearButtonClick?.currentValue;
if (clearButtonClick && clearButtonClick !== changes.clearButtonClick?.previousValue) {
// Clear Button click
this._elementRef.nativeElement.value = '';
this.handleClearButtonReset.emit();
}
else if (
// Selected label is proper value
selectedLabel &&
changes.selectedLabel?.currentValue !== changes.selectedLabel?.previousValue) {
this._elementRef.nativeElement.value = selectedLabel;
}
else if (changes.selectedLabel && !selectedLabel && !this._focused) {
// Selected label is null, but focus is not on input
this._elementRef.nativeElement.value = '';
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectAutocompleteDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SelectAutocompleteDirective, isStandalone: true, selector: "[fudisSelectAutocomplete]", inputs: { selectedLabel: "selectedLabel" }, usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectAutocompleteDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisSelectAutocomplete]' }]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { selectedLabel: [{
type: Input
}] } });
class MultiselectAutocompleteDirective extends SelectAutocompleteBaseDirective {
constructor(_elementRef) {
super(_elementRef);
}
ngOnChanges(changes) {
if (this.enableAutocomplete) {
if (changes.clearButtonClick?.currentValue &&
changes.clearButtonClick?.currentValue !== changes.clearButtonClick?.previousValue) {
// Clear Button click
this._elementRef.nativeElement.value = '';
this.handleClearButtonReset.emit();
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectAutocompleteDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: MultiselectAutocompleteDirective, isStandalone: true, selector: "[fudisMultiselectAutocomplete]", usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectAutocompleteDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisMultiselectAutocomplete]' }]
}], ctorParameters: () => [{ type: i0.ElementRef }] });
class SelectIconsComponent {
constructor(_translationService) {
this._translationService = _translationService;
/**
* Output event for Clear Button click
*/
this.handleClearButtonClick = new EventEmitter();
/**
* Output event for Clear Button focus
*/
this.handleClearButtonFocus = new EventEmitter();
/**
* Output event for Clear Button blur
*/
this.handleClearButtonBlur = new EventEmitter();
/**
* Output event for Clear Button destroy
*/
this.handleClearButtonDestroy = new EventEmitter();
/**
* Observable for status if parent control has value
*/
this._controlValue = new BehaviorSubject(false);
/**
* Translated aria-label for autocomplete close icon button which clears the input
*/
this._translationClearFilterText = new BehaviorSubject('');
this._destroyRef = inject(DestroyRef);
effect(() => {
this._translationClearFilterText.next(_translationService.getTranslations()().SELECT.AUTOCOMPLETE.CLEAR);
});
}
/**
* Click handler for Clear Button
*/
_clearButtonClick(event) {
this.handleClearButtonClick.emit(event);
}
/**
* Focus handler for Clear Button
*/
_handleClearButtonFocus(event) {
this.handleClearButtonFocus.emit(event);
}
/**
* Blur handler for Clear Button
*/
_handleClearButtonBlur(event) {
this.handleClearButtonBlur.emit(event);
}
ngOnChanges(changes) {
if (changes.parentControl?.currentValue !== changes.parentControl?.previousValue) {
this._controlValue.next(!!changes.parentControl?.currentValue?.value);
this._subscription?.unsubscribe();
this._subscription = this.parentControl.valueChanges
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe((value) => {
this._controlValue.next(!!value);
});
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectIconsComponent, deps: [{ token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SelectIconsComponent, isStandalone: true, selector: "fudis-select-icons", inputs: { dropdownOpen: "dropdownOpen", parentVariant: "parentVariant", clearButton: "clearButton", disabled: "disabled", filterText: "filterText", parentControl: "parentControl", parentId: "parentId" }, outputs: { handleClearButtonClick: "handleClearButtonClick", handleClearButtonFocus: "handleClearButtonFocus", handleClearButtonBlur: "handleClearButtonBlur", handleClearButtonDestroy: "handleClearButtonDestroy" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"fudis-select-icons__container\">\n @if (\n parentVariant !== \"autocompleteType\" &&\n (!clearButton || ((_controlValue | async) === false && !filterText))\n ) {\n <fudis-icon\n class=\"fudis-select-icons__icon\"\n [class.fudis-select-icons__icon--disabled]=\"disabled\"\n [icon]=\"'chevron'\"\n [rotate]=\"dropdownOpen ? 'ccw-90' : 'cw-90'\"\n />\n }\n @if (\n (parentVariant === \"autocompleteType\" && !filterText && (_controlValue | async) === false) ||\n (parentVariant === \"autocompleteType\" && !clearButton)\n ) {\n <fudis-icon\n class=\"fudis-select-icons__icon\"\n [class.fudis-select-icons__icon--disabled]=\"disabled\"\n [icon]=\"'search'\"\n />\n }\n</div>\n\n@if (clearButton) {\n @if (_translationClearFilterText | async; as clearText) {\n <fudis-icon-button\n [class.fudis-select-icons__icon--disabled]=\"disabled\"\n [class.fudis-select-icons__icon--hidden]=\"\n !(\n (parentVariant === 'dropdown' && (_controlValue | async)) ||\n (parentVariant === 'autocompleteDropdown' && (filterText || (_controlValue | async))) ||\n (parentVariant === 'autocompleteType' && (filterText || (_controlValue | async)))\n )\n \"\n (handleClick)=\"_clearButtonClick($event)\"\n (handleDestroy)=\"handleClearButtonDestroy.emit()\"\n (handleFocus)=\"_handleClearButtonFocus($event)\"\n (handleBlur)=\"_handleClearButtonBlur($event)\"\n [id]=\"parentId + '-clear-button'\"\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'close'\"\n [ariaLabel]=\"clearText\"\n [disabled]=\"disabled\"\n />\n }\n}\n", styles: [".fudis-select-icons__icon--disabled{pointer-events:none}.fudis-select-icons__icon--hidden{display:none}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }, { kind: "component", type: IconButtonComponent, selector: "fudis-icon-button", inputs: ["ariaLabel", "icon", "asMenuButton"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectIconsComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-select-icons', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent, IconButtonComponent, AsyncPipe], template: "<div class=\"fudis-select-icons__container\">\n @if (\n parentVariant !== \"autocompleteType\" &&\n (!clearButton || ((_controlValue | async) === false && !filterText))\n ) {\n <fudis-icon\n class=\"fudis-select-icons__icon\"\n [class.fudis-select-icons__icon--disabled]=\"disabled\"\n [icon]=\"'chevron'\"\n [rotate]=\"dropdownOpen ? 'ccw-90' : 'cw-90'\"\n />\n }\n @if (\n (parentVariant === \"autocompleteType\" && !filterText && (_controlValue | async) === false) ||\n (parentVariant === \"autocompleteType\" && !clearButton)\n ) {\n <fudis-icon\n class=\"fudis-select-icons__icon\"\n [class.fudis-select-icons__icon--disabled]=\"disabled\"\n [icon]=\"'search'\"\n />\n }\n</div>\n\n@if (clearButton) {\n @if (_translationClearFilterText | async; as clearText) {\n <fudis-icon-button\n [class.fudis-select-icons__icon--disabled]=\"disabled\"\n [class.fudis-select-icons__icon--hidden]=\"\n !(\n (parentVariant === 'dropdown' && (_controlValue | async)) ||\n (parentVariant === 'autocompleteDropdown' && (filterText || (_controlValue | async))) ||\n (parentVariant === 'autocompleteType' && (filterText || (_controlValue | async)))\n )\n \"\n (handleClick)=\"_clearButtonClick($event)\"\n (handleDestroy)=\"handleClearButtonDestroy.emit()\"\n (handleFocus)=\"_handleClearButtonFocus($event)\"\n (handleBlur)=\"_handleClearButtonBlur($event)\"\n [id]=\"parentId + '-clear-button'\"\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'close'\"\n [ariaLabel]=\"clearText\"\n [disabled]=\"disabled\"\n />\n }\n}\n", styles: [".fudis-select-icons__icon--disabled{pointer-events:none}.fudis-select-icons__icon--hidden{display:none}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }], propDecorators: { dropdownOpen: [{
type: Input
}], parentVariant: [{
type: Input
}], clearButton: [{
type: Input
}], disabled: [{
type: Input
}], filterText: [{
type: Input
}], parentControl: [{
type: Input
}], parentId: [{
type: Input
}], handleClearButtonClick: [{
type: Output
}], handleClearButtonFocus: [{
type: Output
}], handleClearButtonBlur: [{
type: Output
}], handleClearButtonDestroy: [{
type: Output
}] } });
class SelectDropdownComponent extends DropdownBaseDirective {
constructor(_translationService) {
super();
this._translationService = _translationService;
/**
* By default, Autocomplete variant will display "No results found" text when there are 0 options
* matching. When combined with 'autocompleteFilter' false, application can set their own
* 'Fetching options...' etc. text while their own filtering is in progress.
*/
this.autocompleteNoResultsText = null;
/**
* Assign Select dropdown to contain single-select or multiselect options (with checkboxes)
*/
this.multiselect = false;
/**
* Set dropdown size
*/
this.size = 'lg';
/**
* Set dropdown open status
*/
this.open = false;
/**
* Internal translated label for situations where no results with current filters were found
*/
this._translationNoResultsFound = new BehaviorSubject('');
/**
* Internal translated label for visible results
*/
this._translationResults = new BehaviorSubject('');
/**
* Internal translated label for visible results
*/
this._translationShowing = new BehaviorSubject('');
/**
* Internal signal mirroring results Input
*/
this._resultsSignal = signal(0, ...(ngDevMode ? [{ debugName: "_resultsSignal" }] : /* istanbul ignore next */ []));
/**
* Internal signal mirroring filterText Input
*/
this._filterTextSignal = signal('', ...(ngDevMode ? [{ debugName: "_filterTextSignal" }] : /* istanbul ignore next */ []));
/**
* Computed signal for building and updating screen reader message
*/
this._liveMessage = computed(() => {
const filterText = this._filterTextSignal();
const results = this._resultsSignal();
// Only announce while dropdown is open
if (!this.open) {
return '';
}
// Reset announcement when input is cleared
if (!filterText) {
return '';
}
const helpText = typeof this.autocompleteHelpText === 'string' ? this.autocompleteHelpText : null;
let message;
if (helpText) {
message = helpText;
}
else {
// Handle the message based on results
const showingText = this._translationShowing.value;
const resultsText = this._translationResults.value;
const noResultsText = this._translationNoResultsFound.value;
if (results > 0) {
message = `${showingText} ${results} ${resultsText}`;
}
else {
message = `${noResultsText}`;
}
}
return message;
}, ...(ngDevMode ? [{ debugName: "_liveMessage" }] : /* istanbul ignore next */ []));
effect(() => {
const translations = _translationService.getTranslations()().SELECT.AUTOCOMPLETE;
this._translationNoResultsFound.next(translations.NO_RESULTS);
this._translationResults.next(translations.RESULTS);
this._translationShowing.next(translations.SHOWING);
});
}
ngOnChanges(changes) {
const newResults = changes.results?.currentValue;
const newFilterText = changes.filterText?.currentValue;
if ((changes.filterText &&
newFilterText !== undefined &&
newFilterText !== changes.filterText?.previousValue) ||
(changes.results && newResults !== undefined && newResults !== changes.results?.previousValue)) {
this._filterTextSignal.set(this.filterText ?? '');
this._resultsSignal.set(this.results ?? 0);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectDropdownComponent, deps: [{ token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SelectDropdownComponent, isStandalone: true, selector: "fudis-select-dropdown", inputs: { selectVariant: "selectVariant", results: "results", autocompleteHelpText: "autocompleteHelpText", autocompleteNoResultsText: "autocompleteNoResultsText", filterText: "filterText", multiselect: "multiselect", parentId: "parentId", size: "size", open: "open" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<span class=\"fudis-visually-hidden\" role=\"status\">{{ _liveMessage() }}</span>\n<div\n #dropdownElement\n [class.fudis-select-dropdown--hidden]=\"selectVariant === 'autocompleteType' && !filterText\"\n class=\"fudis-select-dropdown fudis-input-size__{{ size }}\"\n [class.fudis-select-dropdown--open]=\"open\"\n [class.fudis-select-dropdown__multiselect]=\"multiselect\"\n [attr.id]=\"parentId + '-dropdown'\"\n [attr.aria-labelledby]=\"parentId + '-label'\"\n [attr.aria-multiselectable]=\"multiselect || null\"\n (focus)=\"_dropdownFocus($event)\"\n (blur)=\"_dropdownBlur($event)\"\n role=\"listbox\"\n>\n @if (selectVariant !== \"dropdown\" && open && filterText) {\n @if (autocompleteHelpText !== false) {\n <fudis-body-text\n [class.fudis-select-dropdown__help-text--hidden]=\"!results\"\n class=\"fudis-select-dropdown__help-text fudis-select-dropdown__help-text__first\"\n [variant]=\"'md-light'\"\n ><ng-container *ngTemplateOutlet=\"helpText\"\n /></fudis-body-text>\n }\n <fudis-body-text\n class=\"fudis-select-dropdown__help-text fudis-select-dropdown__help-text__last\"\n [class.fudis-select-dropdown__help-text--hidden]=\"results\"\n [variant]=\"'md-light'\"\n ><ng-container *ngTemplateOutlet=\"noResults\" />\n </fudis-body-text>\n }\n <ng-content></ng-content>\n</div>\n\n<ng-template #helpText>\n @if (autocompleteHelpText) {\n {{ autocompleteHelpText }}\n }\n @if (!autocompleteHelpText) {\n {{ _translationShowing | async }} {{ results }} {{ _translationResults | async }}\n }\n</ng-template>\n\n<ng-template #noResults>\n @if (autocompleteNoResultsText) {\n {{ autocompleteNoResultsText }}\n }\n @if (!autocompleteNoResultsText) {\n {{ _translationNoResultsFound | async }}\n }\n</ng-template>\n", styles: [".fudis-select-dropdown{box-sizing:border-box;margin:0;padding:0;background-color:#fff;z-index:100;border:1px solid #484848;display:none;position:fixed;flex-direction:column;width:100%;max-height:calc(calc(4rem / var(--fudis-rem-multiplier)) * 4);overflow:auto}.fudis-select-dropdown:focus-visible{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-select-dropdown__right{left:0}.fudis-select-dropdown__left{right:0}.fudis-select-dropdown__center{right:initial;left:initial}.fudis-select-dropdown--open{display:flex}.fudis-select-dropdown--hidden{display:none}\n"], dependencies: [{ kind: "component", type: BodyTextComponent, selector: "fudis-body-text", inputs: ["variant", "align", "lang"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectDropdownComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-select-dropdown', changeDetection: ChangeDetectionStrategy.OnPush, imports: [BodyTextComponent, NgTemplateOutlet, AsyncPipe], template: "<span class=\"fudis-visually-hidden\" role=\"status\">{{ _liveMessage() }}</span>\n<div\n #dropdownElement\n [class.fudis-select-dropdown--hidden]=\"selectVariant === 'autocompleteType' && !filterText\"\n class=\"fudis-select-dropdown fudis-input-size__{{ size }}\"\n [class.fudis-select-dropdown--open]=\"open\"\n [class.fudis-select-dropdown__multiselect]=\"multiselect\"\n [attr.id]=\"parentId + '-dropdown'\"\n [attr.aria-labelledby]=\"parentId + '-label'\"\n [attr.aria-multiselectable]=\"multiselect || null\"\n (focus)=\"_dropdownFocus($event)\"\n (blur)=\"_dropdownBlur($event)\"\n role=\"listbox\"\n>\n @if (selectVariant !== \"dropdown\" && open && filterText) {\n @if (autocompleteHelpText !== false) {\n <fudis-body-text\n [class.fudis-select-dropdown__help-text--hidden]=\"!results\"\n class=\"fudis-select-dropdown__help-text fudis-select-dropdown__help-text__first\"\n [variant]=\"'md-light'\"\n ><ng-container *ngTemplateOutlet=\"helpText\"\n /></fudis-body-text>\n }\n <fudis-body-text\n class=\"fudis-select-dropdown__help-text fudis-select-dropdown__help-text__last\"\n [class.fudis-select-dropdown__help-text--hidden]=\"results\"\n [variant]=\"'md-light'\"\n ><ng-container *ngTemplateOutlet=\"noResults\" />\n </fudis-body-text>\n }\n <ng-content></ng-content>\n</div>\n\n<ng-template #helpText>\n @if (autocompleteHelpText) {\n {{ autocompleteHelpText }}\n }\n @if (!autocompleteHelpText) {\n {{ _translationShowing | async }} {{ results }} {{ _translationResults | async }}\n }\n</ng-template>\n\n<ng-template #noResults>\n @if (autocompleteNoResultsText) {\n {{ autocompleteNoResultsText }}\n }\n @if (!autocompleteNoResultsText) {\n {{ _translationNoResultsFound | async }}\n }\n</ng-template>\n", styles: [".fudis-select-dropdown{box-sizing:border-box;margin:0;padding:0;background-color:#fff;z-index:100;border:1px solid #484848;display:none;position:fixed;flex-direction:column;width:100%;max-height:calc(calc(4rem / var(--fudis-rem-multiplier)) * 4);overflow:auto}.fudis-select-dropdown:focus-visible{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-select-dropdown__right{left:0}.fudis-select-dropdown__left{right:0}.fudis-select-dropdown__center{right:initial;left:initial}.fudis-select-dropdown--open{display:flex}.fudis-select-dropdown--hidden{display:none}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }], propDecorators: { selectVariant: [{
type: Input,
args: [{ required: true }]
}], results: [{
type: Input
}], autocompleteHelpText: [{
type: Input
}], autocompleteNoResultsText: [{
type: Input
}], filterText: [{
type: Input
}], multiselect: [{
type: Input
}], parentId: [{
type: Input
}], size: [{
type: Input
}], open: [{
type: Input
}] } });
/**
* Allows selection of a single option from a dropdown list.
*
* Use this component when there are multiple predefined options and user can choose only one value.
* The FormControl value is a `FudisSelectOption` object (not a primitive). Options are projected
* via `<ng-template fudisSelectOptions>` containing `<fudis-select-option>` elements.
*
* Option data should be treated as immutable. If application needs to update option labels
* dynamically, for example after a language change, provide a new options array with new
* `FudisSelectOption` object references instead of mutating existing option objects in place.
* Replacing option objects allows the component to update the selected label and option views
* correctly.
*
* @example
* ```html
* <fudis-select [label]="'Country'" [control]="countryControl">
* <ng-template fudisSelectOptions>
* <fudis-select-option [data]="{ value: 'fi', label: 'Finland' }"></fudis-select-option>
* <fudis-select-option [data]="{ value: 'se', label: 'Sweden' }"></fudis-select-option>
* </ng-template>
* </fudis-select>
* ```;
*/
class SelectComponent extends SelectBaseDirective {
constructor(_document, _dialogService, _idService, _focusService) {
super(_document, _dialogService, _focusService, _idService);
/**
* Value output event on selection change
*/
this.selectionUpdate = new EventEmitter();
}
ngOnInit() {
this._setParentId('select');
}
/**
* Handler for triggered option selection change
*
* @param value Option to be selected
* @param disableSignalEmit Disable signal update to reduce unneeded state updates
*/
handleSelectionChange(value) {
// Check if option clicked is not the same as already selected one. If they are different, then trigger state changes in component and control values
const equalValues = areObjectsDeepEquals(value, this.control.value);
if (!equalValues) {
this.selectionUpdate.emit(value);
this.control.patchValue(value);
}
}
/**
* Checks if currently typed filter text is not same as control label value
*
* @param text Filter text value emitted from autocomplete
*/
_checkIfAutocompleteValueNull(text) {
if (this.control.value && text.toLowerCase() !== this.control.value?.label?.toLowerCase()) {
this.selectionUpdate.emit(null);
this.control.patchValue(null);
}
}
/**
* If control value is updated from the Application, update component's state accordingly
*/
_updateComponentStateFromControlValue() {
const currentLabel = this.control.value?.label;
if (this.variant !== 'dropdown') {
if (currentLabel) {
this.setAutocompleteFilterText(currentLabel);
}
else if (!this._inputFocused) {
this.setAutocompleteFilterText('');
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectComponent, deps: [{ token: DOCUMENT }, { token: FudisDialogService }, { token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SelectComponent, isStandalone: true, selector: "fudis-select", inputs: { control: "control" }, outputs: { selectionUpdate: "selectionUpdate" }, viewQueries: [{ propertyName: "selectCVA", first: true, predicate: SelectControlValueAccessorDirective, descendants: true }], usesInheritance: true, ngImport: i0, template: "@let dropdownOpen = _dropdownOpen();\n@let autocompleteFilterText = _autocompleteFilterText();\n\n<div class=\"fudis-select fudis-input-size__{{ size }}\" [id]=\"id + '-main-wrapper'\">\n <fudis-label\n [text]=\"label\"\n [id]=\"id + '-label'\"\n [for]=\"id\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n >\n </fudis-label>\n <div #selectRef>\n <div class=\"fudis-select__input-wrapper fudis-input-size__{{ size }}\">\n <input\n #inputRef\n fudisSelectControlValueAccessor\n fudisSelectAutocomplete\n [id]=\"id\"\n [attr.id]=\"id\"\n [formControl]=\"control\"\n [class.fudis-select__input__dropdown]=\"variant === 'dropdown'\"\n [class.fudis-select-autocomplete]=\"variant !== 'dropdown'\"\n class=\"fudis-form-input fudis-select__input\"\n [class.fudis-form-input--touched]=\"_afterViewInitDone() && _touched()\"\n (click)=\"!_disabled() && !disabled && _clickInput()\"\n (focus)=\"_selectInputFocus($event)\"\n (blur)=\"_inputBlur($event)\"\n (keydown)=\"_inputKeyDown($event)\"\n (keyup)=\"_inputKeyUp($event)\"\n [enableAutocomplete]=\"variant !== 'dropdown'\"\n [selectedLabel]=\"_selectedLabel()\"\n [typeThreshold]=\"variant === 'autocompleteType' ? 3 : 0\"\n [filterText]=\"autocompleteFilterText\"\n [clearButtonClick]=\"_clearButtonClickTrigger()\"\n (handleDropdownOpen)=\"openDropdown()\"\n (handleDropdownClose)=\"closeDropdown()\"\n (handleClearButtonReset)=\"_clearButtonClickTrigger.set(false)\"\n (handleFilterTextUpdate)=\"setAutocompleteFilterText($event)\"\n type=\"text\"\n autocomplete=\"off\"\n aria-haspopup=\"listbox\"\n role=\"combobox\"\n [attr.readonly]=\"variant === 'dropdown' ? true : null\"\n [attr.aria-autocomplete]=\"variant === 'dropdown' ? null : 'list'\"\n [attr.placeholder]=\"placeholder\"\n [attr.aria-controls]=\"_optionsLoadedOnce() ? id + '-dropdown' : null\"\n [attr.tabindex]=\"!_disabled() && !disabled ? null : -1\"\n [attr.aria-expanded]=\"dropdownOpen\"\n [attr.aria-disabled]=\"_disabled() || disabled || null\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-labelledby]=\"id + '-label'\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.aria-invalid]=\"(_invalid() && _touched()) || null\"\n />\n <div #selectIconsRef class=\"fudis-select__input-wrapper__icons\">\n <fudis-select-icons\n [filterText]=\"!!autocompleteFilterText\"\n [parentControl]=\"control\"\n [parentVariant]=\"variant\"\n [dropdownOpen]=\"dropdownOpen\"\n [parentId]=\"id\"\n (handleClearButtonClick)=\"_clearButtonClick()\"\n (handleClearButtonFocus)=\"_setClearButtonFocusState($event, true)\"\n (handleClearButtonBlur)=\"_setClearButtonFocusState($event, false)\"\n (handleClearButtonDestroy)=\"_setClearButtonFocusFalse()\"\n [clearButton]=\"selectionClearButton\"\n [disabled]=\"_disabled() || disabled\"\n />\n </div>\n </div>\n @if (_optionsLoadedOnce() && !_disabled() && !disabled) {\n <fudis-select-dropdown\n #dropdownRef\n [open]=\"dropdownOpen\"\n [selectVariant]=\"variant\"\n [results]=\"_resultsLength()\"\n [autocompleteHelpText]=\"autocompleteHelpText\"\n [autocompleteNoResultsText]=\"autocompleteNoResultsText\"\n [filterText]=\"autocompleteFilterText\"\n [size]=\"size\"\n [parentId]=\"id\"\n (handleBlur)=\"_dropdownBlur($event)\"\n (handleFocus)=\"_dropdownFocus($event)\"\n >\n @if (_selectOptionsDirective) {\n <ng-container *ngTemplateOutlet=\"_selectOptionsDirective!.templateRef\" />\n }\n </fudis-select-dropdown>\n }\n </div>\n @if (!disableGuidance) {\n <fudis-guidance [inputLabel]=\"label\" [helpText]=\"helpText\" [for]=\"id\" [control]=\"control\">\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n</div>\n", styles: [".fudis-select{position:relative;width:100%}.fudis-select__input-wrapper{position:relative}.fudis-select__input-wrapper__icons{position:absolute;top:50%;right:calc(.25rem / var(--fudis-rem-multiplier));transform:translateY(-50%)}.fudis-select__input{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;border-radius:2px;box-sizing:border-box;padding:calc(.25rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier)) calc(.25rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));width:100%;height:calc(2.5rem / var(--fudis-rem-multiplier));max-height:calc(2.5rem / var(--fudis-rem-multiplier));overflow:hidden;text-overflow:ellipsis;line-height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-select__input::placeholder{color:#727272}.fudis-select__input:focus,.fudis-select__input:focus-visible{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-select__input__label,.fudis-select__input__placeholder{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fudis-select__input__label--hidden,.fudis-select__input__placeholder--hidden{display:none}@media screen and (max-width:35.99em){.fudis-select__input__label,.fudis-select__input__placeholder{max-width:calc(100vw - 5.5rem / var(--fudis-rem-multiplier))}}.fudis-select__input__placeholder{color:#727272}.fudis-select__input__dropdown{cursor:default;caret-color:transparent}.fudis-select.fudis-input-size__xs .fudis-select__input{padding-left:calc(.5rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "component", type: LabelComponent, selector: "fudis-label", inputs: ["id", "text", "for", "required"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: SelectControlValueAccessorDirective, selector: "[fudisSelectControlValueAccessor]", inputs: ["filterText"] }, { kind: "directive", type: SelectAutocompleteDirective, selector: "[fudisSelectAutocomplete]", inputs: ["selectedLabel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: SelectIconsComponent, selector: "fudis-select-icons", inputs: ["dropdownOpen", "parentVariant", "clearButton", "disabled", "filterText", "parentControl", "parentId"], outputs: ["handleClearButtonClick", "handleClearButtonFocus", "handleClearButtonBlur", "handleClearButtonDestroy"] }, { kind: "component", type: SelectDropdownComponent, selector: "fudis-select-dropdown", inputs: ["selectVariant", "results", "autocompleteHelpText", "autocompleteNoResultsText", "filterText", "multiselect", "parentId", "size", "open"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: GuidanceComponent, selector: "fudis-guidance", inputs: ["for", "inputLabel", "control", "formGroup", "helpText", "maxLength", "selectedOption", "groupBlurredOut", "groupHelpTextHidden"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-select', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
LabelComponent,
FormsModule,
SelectControlValueAccessorDirective,
SelectAutocompleteDirective,
ReactiveFormsModule,
SelectIconsComponent,
SelectDropdownComponent,
NgTemplateOutlet,
GuidanceComponent,
AsyncPipe,
], template: "@let dropdownOpen = _dropdownOpen();\n@let autocompleteFilterText = _autocompleteFilterText();\n\n<div class=\"fudis-select fudis-input-size__{{ size }}\" [id]=\"id + '-main-wrapper'\">\n <fudis-label\n [text]=\"label\"\n [id]=\"id + '-label'\"\n [for]=\"id\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n >\n </fudis-label>\n <div #selectRef>\n <div class=\"fudis-select__input-wrapper fudis-input-size__{{ size }}\">\n <input\n #inputRef\n fudisSelectControlValueAccessor\n fudisSelectAutocomplete\n [id]=\"id\"\n [attr.id]=\"id\"\n [formControl]=\"control\"\n [class.fudis-select__input__dropdown]=\"variant === 'dropdown'\"\n [class.fudis-select-autocomplete]=\"variant !== 'dropdown'\"\n class=\"fudis-form-input fudis-select__input\"\n [class.fudis-form-input--touched]=\"_afterViewInitDone() && _touched()\"\n (click)=\"!_disabled() && !disabled && _clickInput()\"\n (focus)=\"_selectInputFocus($event)\"\n (blur)=\"_inputBlur($event)\"\n (keydown)=\"_inputKeyDown($event)\"\n (keyup)=\"_inputKeyUp($event)\"\n [enableAutocomplete]=\"variant !== 'dropdown'\"\n [selectedLabel]=\"_selectedLabel()\"\n [typeThreshold]=\"variant === 'autocompleteType' ? 3 : 0\"\n [filterText]=\"autocompleteFilterText\"\n [clearButtonClick]=\"_clearButtonClickTrigger()\"\n (handleDropdownOpen)=\"openDropdown()\"\n (handleDropdownClose)=\"closeDropdown()\"\n (handleClearButtonReset)=\"_clearButtonClickTrigger.set(false)\"\n (handleFilterTextUpdate)=\"setAutocompleteFilterText($event)\"\n type=\"text\"\n autocomplete=\"off\"\n aria-haspopup=\"listbox\"\n role=\"combobox\"\n [attr.readonly]=\"variant === 'dropdown' ? true : null\"\n [attr.aria-autocomplete]=\"variant === 'dropdown' ? null : 'list'\"\n [attr.placeholder]=\"placeholder\"\n [attr.aria-controls]=\"_optionsLoadedOnce() ? id + '-dropdown' : null\"\n [attr.tabindex]=\"!_disabled() && !disabled ? null : -1\"\n [attr.aria-expanded]=\"dropdownOpen\"\n [attr.aria-disabled]=\"_disabled() || disabled || null\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-labelledby]=\"id + '-label'\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.aria-invalid]=\"(_invalid() && _touched()) || null\"\n />\n <div #selectIconsRef class=\"fudis-select__input-wrapper__icons\">\n <fudis-select-icons\n [filterText]=\"!!autocompleteFilterText\"\n [parentControl]=\"control\"\n [parentVariant]=\"variant\"\n [dropdownOpen]=\"dropdownOpen\"\n [parentId]=\"id\"\n (handleClearButtonClick)=\"_clearButtonClick()\"\n (handleClearButtonFocus)=\"_setClearButtonFocusState($event, true)\"\n (handleClearButtonBlur)=\"_setClearButtonFocusState($event, false)\"\n (handleClearButtonDestroy)=\"_setClearButtonFocusFalse()\"\n [clearButton]=\"selectionClearButton\"\n [disabled]=\"_disabled() || disabled\"\n />\n </div>\n </div>\n @if (_optionsLoadedOnce() && !_disabled() && !disabled) {\n <fudis-select-dropdown\n #dropdownRef\n [open]=\"dropdownOpen\"\n [selectVariant]=\"variant\"\n [results]=\"_resultsLength()\"\n [autocompleteHelpText]=\"autocompleteHelpText\"\n [autocompleteNoResultsText]=\"autocompleteNoResultsText\"\n [filterText]=\"autocompleteFilterText\"\n [size]=\"size\"\n [parentId]=\"id\"\n (handleBlur)=\"_dropdownBlur($event)\"\n (handleFocus)=\"_dropdownFocus($event)\"\n >\n @if (_selectOptionsDirective) {\n <ng-container *ngTemplateOutlet=\"_selectOptionsDirective!.templateRef\" />\n }\n </fudis-select-dropdown>\n }\n </div>\n @if (!disableGuidance) {\n <fudis-guidance [inputLabel]=\"label\" [helpText]=\"helpText\" [for]=\"id\" [control]=\"control\">\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n</div>\n", styles: [".fudis-select{position:relative;width:100%}.fudis-select__input-wrapper{position:relative}.fudis-select__input-wrapper__icons{position:absolute;top:50%;right:calc(.25rem / var(--fudis-rem-multiplier));transform:translateY(-50%)}.fudis-select__input{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;border-radius:2px;box-sizing:border-box;padding:calc(.25rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier)) calc(.25rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));width:100%;height:calc(2.5rem / var(--fudis-rem-multiplier));max-height:calc(2.5rem / var(--fudis-rem-multiplier));overflow:hidden;text-overflow:ellipsis;line-height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-select__input::placeholder{color:#727272}.fudis-select__input:focus,.fudis-select__input:focus-visible{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-select__input__label,.fudis-select__input__placeholder{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fudis-select__input__label--hidden,.fudis-select__input__placeholder--hidden{display:none}@media screen and (max-width:35.99em){.fudis-select__input__label,.fudis-select__input__placeholder{max-width:calc(100vw - 5.5rem / var(--fudis-rem-multiplier))}}.fudis-select__input__placeholder{color:#727272}.fudis-select__input__dropdown{cursor:default;caret-color:transparent}.fudis-select.fudis-input-size__xs .fudis-select__input{padding-left:calc(.5rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: FudisDialogService }, { type: FudisIdService }, { type: FudisFocusService }], propDecorators: { selectCVA: [{
type: ViewChild,
args: [SelectControlValueAccessorDirective]
}], control: [{
type: Input,
args: [{ required: true }]
}], selectionUpdate: [{
type: Output
}] } });
class MultiselectChipListComponent {
constructor(_translationService) {
this._translationService = _translationService;
/**
* Output for removed chip index in selectedItems
*/
this.handleClick = new EventEmitter();
}
/**
* Focuses to the sibling and emits clicked index
*
* @param index Clicked index
*/
_clickChip(clickedOption, index) {
this.handleClick.emit(clickedOption);
setTimeout(() => {
const buttons = this._chipListRef.nativeElement.querySelectorAll('button');
if (index === 0 && buttons[0]) {
buttons[0].focus();
}
else if (buttons[index - 1]) {
buttons[index - 1].focus();
}
}, 50);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectChipListComponent, deps: [{ token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MultiselectChipListComponent, isStandalone: true, selector: "fudis-multiselect-chip-list", inputs: { selectedItems: "selectedItems", parentId: "parentId" }, outputs: { handleClick: "handleClick" }, viewQueries: [{ propertyName: "_chipListRef", first: true, predicate: ["chipListRef"], descendants: true }], ngImport: i0, template: "<ul #chipListRef class=\"fudis-multiselect-chip-list\" [attr.id]=\"parentId + '-selected-items'\">\n @for (item of selectedItems; track item.value; let index = $index) {\n <li class=\"fudis-multiselect-chip-list__item\">\n <button class=\"fudis-multiselect-chip-list__item__button\" (click)=\"_clickChip(item, index)\">\n <fudis-icon [icon]=\"'close'\" [color]=\"'white'\"></fudis-icon>\n <span class=\"fudis-multiselect-chip-list__item__button__label\">{{ item.label }}</span>\n <span class=\"fudis-visually-hidden\">\n {{ _translationService.getTranslations()().SELECT.MULTISELECT.REMOVE_ITEM }}\n </span>\n </button>\n </li>\n }\n</ul>\n", styles: [".fudis-multiselect-chip-list{box-sizing:border-box;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:calc(.25rem / var(--fudis-rem-multiplier));padding-top:calc(.25rem / var(--fudis-rem-multiplier));list-style:none}.fudis-multiselect-chip-list__item__button{background-color:#484848;color:#fff;text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400;border:none;box-sizing:border-box;margin:0;padding:0;display:flex;text-align:start;cursor:pointer}.fudis-multiselect-chip-list__item__button:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-multiselect-chip-list__item__button__label{padding:calc(.5rem / var(--fudis-rem-multiplier)) calc(.5rem / var(--fudis-rem-multiplier)) calc(.5rem / var(--fudis-rem-multiplier)) 0}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectChipListComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-multiselect-chip-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent], template: "<ul #chipListRef class=\"fudis-multiselect-chip-list\" [attr.id]=\"parentId + '-selected-items'\">\n @for (item of selectedItems; track item.value; let index = $index) {\n <li class=\"fudis-multiselect-chip-list__item\">\n <button class=\"fudis-multiselect-chip-list__item__button\" (click)=\"_clickChip(item, index)\">\n <fudis-icon [icon]=\"'close'\" [color]=\"'white'\"></fudis-icon>\n <span class=\"fudis-multiselect-chip-list__item__button__label\">{{ item.label }}</span>\n <span class=\"fudis-visually-hidden\">\n {{ _translationService.getTranslations()().SELECT.MULTISELECT.REMOVE_ITEM }}\n </span>\n </button>\n </li>\n }\n</ul>\n", styles: [".fudis-multiselect-chip-list{box-sizing:border-box;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:calc(.25rem / var(--fudis-rem-multiplier));padding-top:calc(.25rem / var(--fudis-rem-multiplier));list-style:none}.fudis-multiselect-chip-list__item__button{background-color:#484848;color:#fff;text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400;border:none;box-sizing:border-box;margin:0;padding:0;display:flex;text-align:start;cursor:pointer}.fudis-multiselect-chip-list__item__button:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-multiselect-chip-list__item__button__label{padding:calc(.5rem / var(--fudis-rem-multiplier)) calc(.5rem / var(--fudis-rem-multiplier)) calc(.5rem / var(--fudis-rem-multiplier)) 0}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }], propDecorators: { _chipListRef: [{
type: ViewChild,
args: ['chipListRef']
}], selectedItems: [{
type: Input
}], parentId: [{
type: Input
}], handleClick: [{
type: Output
}] } });
/**
* Allows selection of multiple options from a dropdown list.
*
* Use this component when there are multiple predefined options and user can choose more than one
* value. The FormControl value is a `FudisSelectOption[]` array (not primitives). Options are
* projected via `<ng-template fudisSelectOptions>` containing `<fudis-multiselect-option>`
* elements.
*
* Option data should be treated as immutable. If application needs to update option labels
* dynamically, for example after a language change, provide a new options array with new
* `FudisSelectOption` object references instead of mutating existing option objects in place.
* Replacing option objects allows the component to update selected values and option labels
* correctly.
*
* @example
* ```html
* <fudis-multiselect [label]="'Countries'" [control]="countriesControl">
* <ng-template fudisSelectOptions>
* <fudis-multiselect-option [data]="{ value: 'fi', label: 'Finland' }"></fudis-multiselect-option>
* <fudis-multiselect-option [data]="{ value: 'se', label: 'Sweden' }"></fudis-multiselect-option>
* </ng-template>
* </fudis-multiselect>
* ```;
*/
class MultiselectComponent extends SelectBaseDirective {
constructor(_document, _translationService, _dialogService, _idService, _focusService) {
super(_document, _dialogService, _focusService, _idService);
this._translationService = _translationService;
/**
* Hide or show selection chips rendered below input
*/
this.showSelectionChips = true;
/**
* Value output event on selection change
*/
this.selectionUpdate = new EventEmitter();
/**
* When app language and option labels are changed, selected Multiselect Options push themselves
* here, which will be then used to update visible UI labels managed by MultiselectCVA
*/
this.selectedOptionsFromLangChange = [];
/**
* When selecting / deselecting options, variable for storing them in the order of their id's
* (usually the DOM order)
*/
this._sortedSelectedOptions = signal(null, ...(ngDevMode ? [{ debugName: "_sortedSelectedOptions" }] : /* istanbul ignore next */ []));
}
/**
* Set component's id and subscribe to value changes for form control coming from application
*/
ngOnInit() {
this._setParentId('multiselect');
}
/**
* Handler for adding / removing selections
*
* @param option FudisSelectOption to handle
* @param type Add or remove multiselect option
*/
handleMultiSelectionChange(option, type) {
let updatedValue = this.control.value;
if (type === 'remove' && updatedValue) {
updatedValue = updatedValue.filter((item) => {
return item.value !== option.value;
});
}
else if (!updatedValue) {
updatedValue = [option];
}
else if (type === 'add') {
updatedValue.push(option);
}
if (updatedValue?.length === 0) {
this.selectionUpdate.emit(null);
this.control.patchValue(null);
}
else {
this.selectionUpdate.emit(updatedValue);
this.control.patchValue(updatedValue);
}
}
// Reason for the cast and any type: (known Angular limitation) attribute directives cannot propagate generics from their parent.
// Angular will always instantiate the MultiselectControlValueAccessorDirective with its default generic (string).
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_updateSortedSelectedOptions(newValue) {
this._sortedSelectedOptions.set(newValue);
}
/**
* Handle chip remove. If there are no selections done, focus back to input on last item removal.
*
* @param option Removed option
*/
_handleRemoveChip(option) {
this.handleMultiSelectionChange(option, 'remove');
if (!this.control.value) {
this._focusToSelectInput();
this.setAutocompleteFilterText('');
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectComponent, deps: [{ token: DOCUMENT }, { token: FudisTranslationService }, { token: FudisDialogService }, { token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MultiselectComponent, isStandalone: true, selector: "fudis-multiselect", inputs: { control: "control", showSelectionChips: "showSelectionChips" }, outputs: { selectionUpdate: "selectionUpdate" }, viewQueries: [{ propertyName: "_multiselectCVA", first: true, predicate: MultiselectControlValueAccessorDirective, descendants: true }], usesInheritance: true, ngImport: i0, template: "@let dropdownOpen = _dropdownOpen();\n@let sortedSelectedOptions = _sortedSelectedOptions();\n@let autocompleteFilterText = _autocompleteFilterText();\n\n<div\n role=\"application\"\n class=\"fudis-select fudis-input-size__{{ size }}\"\n [id]=\"id + '-main-wrapper'\"\n>\n <fudis-label\n [text]=\"label\"\n [id]=\"id + '-label'\"\n [for]=\"id\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n >\n </fudis-label>\n <div #selectRef>\n <div class=\"fudis-select__input-wrapper\">\n <input\n #inputRef\n fudisMultiselectControlValueAccessor\n fudisMultiselectAutocomplete\n [id]=\"id\"\n [formControl]=\"control\"\n class=\"fudis-form-input fudis-select__input\"\n [class.fudis-select__input__dropdown]=\"variant === 'dropdown'\"\n [class.fudis-select-autocomplete]=\"variant !== 'dropdown'\"\n [class.fudis-form-input--touched]=\"_afterViewInitDone() && _touched()\"\n (click)=\"!_disabled() && !disabled && _clickInput()\"\n (focus)=\"_selectInputFocus($event)\"\n (blur)=\"_inputBlur($event)\"\n (keydown)=\"_inputKeyDown($event)\"\n (keyup)=\"_inputKeyUp($event)\"\n [enableAutocomplete]=\"variant !== 'dropdown'\"\n [typeThreshold]=\"variant === 'autocompleteType' ? 3 : 0\"\n [clearButtonClick]=\"_clearButtonClickTrigger()\"\n (handleSortedSelectedOptions)=\"_updateSortedSelectedOptions($event)\"\n (handleClearButtonReset)=\"_clearButtonClickTrigger.set(false)\"\n (handleDropdownOpen)=\"openDropdown()\"\n (handleDropdownClose)=\"closeDropdown()\"\n (handleFilterTextUpdate)=\"setAutocompleteFilterText($event)\"\n type=\"text\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.id]=\"id\"\n [attr.readonly]=\"variant === 'dropdown' ? true : null\"\n [attr.aria-autocomplete]=\"variant === 'dropdown' ? null : 'list'\"\n [attr.placeholder]=\"placeholder\"\n [attr.aria-controls]=\"_optionsLoadedOnce() ? id + '-dropdown' : null\"\n [attr.tabindex]=\"!_disabled() && !disabled ? null : -1\"\n [attr.aria-expanded]=\"dropdownOpen\"\n [attr.aria-disabled]=\"_disabled() || disabled\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-labelledby]=\"id + '-label'\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.aria-invalid]=\"(_invalid() && _touched()) || null\"\n />\n <div class=\"fudis-select__input-wrapper__icons\">\n @if (size !== \"xs\") {\n <fudis-select-icons\n [filterText]=\"!!autocompleteFilterText\"\n [parentControl]=\"control\"\n [parentVariant]=\"variant\"\n [parentId]=\"id\"\n [dropdownOpen]=\"dropdownOpen\"\n (handleClearButtonClick)=\"_clearButtonClick()\"\n (handleClearButtonFocus)=\"_setClearButtonFocusState($event, true)\"\n (handleClearButtonBlur)=\"_setClearButtonFocusState($event, false)\"\n [clearButton]=\"selectionClearButton\"\n [disabled]=\"_disabled() || disabled\"\n />\n }\n </div>\n </div>\n @if (_optionsLoadedOnce() && !_disabled() && !disabled) {\n <fudis-select-dropdown\n #dropdownRef\n [open]=\"dropdownOpen\"\n [selectVariant]=\"variant\"\n [results]=\"_resultsLength()\"\n [autocompleteNoResultsText]=\"autocompleteNoResultsText\"\n [autocompleteHelpText]=\"autocompleteHelpText\"\n [filterText]=\"autocompleteFilterText\"\n [multiselect]=\"true\"\n [parentId]=\"id\"\n [size]=\"size\"\n >\n @if (_selectOptionsDirective) {\n <ng-container *ngTemplateOutlet=\"_selectOptionsDirective!.templateRef\" />\n }\n </fudis-select-dropdown>\n }\n </div>\n @if (!disableGuidance) {\n <fudis-guidance [inputLabel]=\"label\" [helpText]=\"helpText\" [for]=\"id\" [control]=\"control\">\n <ng-content select=\"fudis-error-message\"\n /></fudis-guidance>\n }\n\n @if (showSelectionChips && sortedSelectedOptions && _enabled() && !disabled) {\n <span [attr.id]=\"id + '-remove-items'\" class=\"fudis-visually-hidden\">{{\n _translationService.getTranslations()().SELECT.MULTISELECT.REMOVE_ITEMS\n }}</span>\n <fudis-multiselect-chip-list\n [selectedItems]=\"sortedSelectedOptions\"\n [parentId]=\"id\"\n (handleClick)=\"_handleRemoveChip($event)\"\n />\n }\n</div>\n", styles: [".fudis-select{position:relative;width:100%}.fudis-select__input-wrapper{position:relative}.fudis-select__input-wrapper__icons{position:absolute;top:50%;right:calc(.25rem / var(--fudis-rem-multiplier));transform:translateY(-50%)}.fudis-select__input{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;border-radius:2px;box-sizing:border-box;padding:calc(.25rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier)) calc(.25rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));width:100%;height:calc(2.5rem / var(--fudis-rem-multiplier));max-height:calc(2.5rem / var(--fudis-rem-multiplier));overflow:hidden;text-overflow:ellipsis;line-height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-select__input::placeholder{color:#727272}.fudis-select__input:focus,.fudis-select__input:focus-visible{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-select__input__label,.fudis-select__input__placeholder{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fudis-select__input__label--hidden,.fudis-select__input__placeholder--hidden{display:none}@media screen and (max-width:35.99em){.fudis-select__input__label,.fudis-select__input__placeholder{max-width:calc(100vw - 5.5rem / var(--fudis-rem-multiplier))}}.fudis-select__input__placeholder{color:#727272}.fudis-select__input__dropdown{cursor:default;caret-color:transparent}.fudis-select.fudis-input-size__xs .fudis-select__input{padding-left:calc(.5rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "component", type: LabelComponent, selector: "fudis-label", inputs: ["id", "text", "for", "required"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: MultiselectControlValueAccessorDirective, selector: "[fudisMultiselectControlValueAccessor]", inputs: ["id", "enableAutocomplete"], outputs: ["handleSortedSelectedOptions"] }, { kind: "directive", type: MultiselectAutocompleteDirective, selector: "[fudisMultiselectAutocomplete]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: SelectIconsComponent, selector: "fudis-select-icons", inputs: ["dropdownOpen", "parentVariant", "clearButton", "disabled", "filterText", "parentControl", "parentId"], outputs: ["handleClearButtonClick", "handleClearButtonFocus", "handleClearButtonBlur", "handleClearButtonDestroy"] }, { kind: "component", type: SelectDropdownComponent, selector: "fudis-select-dropdown", inputs: ["selectVariant", "results", "autocompleteHelpText", "autocompleteNoResultsText", "filterText", "multiselect", "parentId", "size", "open"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: GuidanceComponent, selector: "fudis-guidance", inputs: ["for", "inputLabel", "control", "formGroup", "helpText", "maxLength", "selectedOption", "groupBlurredOut", "groupHelpTextHidden"] }, { kind: "component", type: MultiselectChipListComponent, selector: "fudis-multiselect-chip-list", inputs: ["selectedItems", "parentId"], outputs: ["handleClick"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-multiselect', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
LabelComponent,
FormsModule,
MultiselectControlValueAccessorDirective,
MultiselectAutocompleteDirective,
ReactiveFormsModule,
SelectIconsComponent,
SelectDropdownComponent,
NgTemplateOutlet,
GuidanceComponent,
MultiselectChipListComponent,
AsyncPipe,
], template: "@let dropdownOpen = _dropdownOpen();\n@let sortedSelectedOptions = _sortedSelectedOptions();\n@let autocompleteFilterText = _autocompleteFilterText();\n\n<div\n role=\"application\"\n class=\"fudis-select fudis-input-size__{{ size }}\"\n [id]=\"id + '-main-wrapper'\"\n>\n <fudis-label\n [text]=\"label\"\n [id]=\"id + '-label'\"\n [for]=\"id\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n >\n </fudis-label>\n <div #selectRef>\n <div class=\"fudis-select__input-wrapper\">\n <input\n #inputRef\n fudisMultiselectControlValueAccessor\n fudisMultiselectAutocomplete\n [id]=\"id\"\n [formControl]=\"control\"\n class=\"fudis-form-input fudis-select__input\"\n [class.fudis-select__input__dropdown]=\"variant === 'dropdown'\"\n [class.fudis-select-autocomplete]=\"variant !== 'dropdown'\"\n [class.fudis-form-input--touched]=\"_afterViewInitDone() && _touched()\"\n (click)=\"!_disabled() && !disabled && _clickInput()\"\n (focus)=\"_selectInputFocus($event)\"\n (blur)=\"_inputBlur($event)\"\n (keydown)=\"_inputKeyDown($event)\"\n (keyup)=\"_inputKeyUp($event)\"\n [enableAutocomplete]=\"variant !== 'dropdown'\"\n [typeThreshold]=\"variant === 'autocompleteType' ? 3 : 0\"\n [clearButtonClick]=\"_clearButtonClickTrigger()\"\n (handleSortedSelectedOptions)=\"_updateSortedSelectedOptions($event)\"\n (handleClearButtonReset)=\"_clearButtonClickTrigger.set(false)\"\n (handleDropdownOpen)=\"openDropdown()\"\n (handleDropdownClose)=\"closeDropdown()\"\n (handleFilterTextUpdate)=\"setAutocompleteFilterText($event)\"\n type=\"text\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.id]=\"id\"\n [attr.readonly]=\"variant === 'dropdown' ? true : null\"\n [attr.aria-autocomplete]=\"variant === 'dropdown' ? null : 'list'\"\n [attr.placeholder]=\"placeholder\"\n [attr.aria-controls]=\"_optionsLoadedOnce() ? id + '-dropdown' : null\"\n [attr.tabindex]=\"!_disabled() && !disabled ? null : -1\"\n [attr.aria-expanded]=\"dropdownOpen\"\n [attr.aria-disabled]=\"_disabled() || disabled\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-labelledby]=\"id + '-label'\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.aria-invalid]=\"(_invalid() && _touched()) || null\"\n />\n <div class=\"fudis-select__input-wrapper__icons\">\n @if (size !== \"xs\") {\n <fudis-select-icons\n [filterText]=\"!!autocompleteFilterText\"\n [parentControl]=\"control\"\n [parentVariant]=\"variant\"\n [parentId]=\"id\"\n [dropdownOpen]=\"dropdownOpen\"\n (handleClearButtonClick)=\"_clearButtonClick()\"\n (handleClearButtonFocus)=\"_setClearButtonFocusState($event, true)\"\n (handleClearButtonBlur)=\"_setClearButtonFocusState($event, false)\"\n [clearButton]=\"selectionClearButton\"\n [disabled]=\"_disabled() || disabled\"\n />\n }\n </div>\n </div>\n @if (_optionsLoadedOnce() && !_disabled() && !disabled) {\n <fudis-select-dropdown\n #dropdownRef\n [open]=\"dropdownOpen\"\n [selectVariant]=\"variant\"\n [results]=\"_resultsLength()\"\n [autocompleteNoResultsText]=\"autocompleteNoResultsText\"\n [autocompleteHelpText]=\"autocompleteHelpText\"\n [filterText]=\"autocompleteFilterText\"\n [multiselect]=\"true\"\n [parentId]=\"id\"\n [size]=\"size\"\n >\n @if (_selectOptionsDirective) {\n <ng-container *ngTemplateOutlet=\"_selectOptionsDirective!.templateRef\" />\n }\n </fudis-select-dropdown>\n }\n </div>\n @if (!disableGuidance) {\n <fudis-guidance [inputLabel]=\"label\" [helpText]=\"helpText\" [for]=\"id\" [control]=\"control\">\n <ng-content select=\"fudis-error-message\"\n /></fudis-guidance>\n }\n\n @if (showSelectionChips && sortedSelectedOptions && _enabled() && !disabled) {\n <span [attr.id]=\"id + '-remove-items'\" class=\"fudis-visually-hidden\">{{\n _translationService.getTranslations()().SELECT.MULTISELECT.REMOVE_ITEMS\n }}</span>\n <fudis-multiselect-chip-list\n [selectedItems]=\"sortedSelectedOptions\"\n [parentId]=\"id\"\n (handleClick)=\"_handleRemoveChip($event)\"\n />\n }\n</div>\n", styles: [".fudis-select{position:relative;width:100%}.fudis-select__input-wrapper{position:relative}.fudis-select__input-wrapper__icons{position:absolute;top:50%;right:calc(.25rem / var(--fudis-rem-multiplier));transform:translateY(-50%)}.fudis-select__input{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;border-radius:2px;box-sizing:border-box;padding:calc(.25rem / var(--fudis-rem-multiplier)) calc(2rem / var(--fudis-rem-multiplier)) calc(.25rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));width:100%;height:calc(2.5rem / var(--fudis-rem-multiplier));max-height:calc(2.5rem / var(--fudis-rem-multiplier));overflow:hidden;text-overflow:ellipsis;line-height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-select__input::placeholder{color:#727272}.fudis-select__input:focus,.fudis-select__input:focus-visible{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-select__input__label,.fudis-select__input__placeholder{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fudis-select__input__label--hidden,.fudis-select__input__placeholder--hidden{display:none}@media screen and (max-width:35.99em){.fudis-select__input__label,.fudis-select__input__placeholder{max-width:calc(100vw - 5.5rem / var(--fudis-rem-multiplier))}}.fudis-select__input__placeholder{color:#727272}.fudis-select__input__dropdown{cursor:default;caret-color:transparent}.fudis-select.fudis-input-size__xs .fudis-select__input{padding-left:calc(.5rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: FudisTranslationService }, { type: FudisDialogService }, { type: FudisIdService }, { type: FudisFocusService }], propDecorators: { _multiselectCVA: [{
type: ViewChild,
args: [MultiselectControlValueAccessorDirective]
}], control: [{
type: Input,
args: [{ required: true }]
}], showSelectionChips: [{
type: Input
}], selectionUpdate: [{
type: Output
}] } });
/**
* Groups select options under a common category.
*
* Use this component to organize related options to improve readability.
*/
class SelectGroupComponent {
constructor(_idService, _parentSelect, _parentMultiselect) {
this._idService = _idService;
this._parentSelect = _parentSelect;
this._parentMultiselect = _parentMultiselect;
/**
* Checks if the select group has content and if the group label should be visible or not.
*/
this._visibleOptions = signal([], ...(ngDevMode ? [{ debugName: "_visibleOptions" }] : /* istanbul ignore next */ []));
if (_parentSelect) {
this.id = this._idService.getNewGroupId('select', this._parentSelect.id);
}
else if (_parentMultiselect) {
this.id = this._idService.getNewGroupId('multiselect', this._parentMultiselect.id);
}
}
/**
* Called from SelectOption and MultiselectOption to set if the option is visible or not
*
* @param value Value of option
* @param visible State of option's visibility
*/
setOptionVisibility(value, visible) {
this._visibleOptions.set(setVisibleOptionsList(this._visibleOptions(), value, visible));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectGroupComponent, deps: [{ token: FudisIdService }, { token: SelectComponent, host: true, optional: true }, { token: MultiselectComponent, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: SelectGroupComponent, isStandalone: true, selector: "fudis-select-group, fudis-multiselect-group", inputs: { label: "label" }, ngImport: i0, template: "<div\n class=\"fudis-select-group\"\n [class.fudis-select-group--hidden]=\"_visibleOptions().length === 0\"\n [attr.aria-labelledby]=\"id\"\n role=\"group\"\n>\n <span class=\"fudis-select-group__label\" [attr.aria-hidden]=\"true\" [attr.id]=\"id\">{{\n label\n }}</span>\n <ng-content></ng-content>\n</div>\n", styles: [".fudis-select-group__label{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:300;color:#727272;box-sizing:border-box;display:flex;align-items:center;border-top:1px solid #d4d4d4;padding-left:calc(.5rem / var(--fudis-rem-multiplier));min-height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-select-group--hidden{display:none;visibility:hidden}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectGroupComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-select-group, fudis-multiselect-group', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"fudis-select-group\"\n [class.fudis-select-group--hidden]=\"_visibleOptions().length === 0\"\n [attr.aria-labelledby]=\"id\"\n role=\"group\"\n>\n <span class=\"fudis-select-group__label\" [attr.aria-hidden]=\"true\" [attr.id]=\"id\">{{\n label\n }}</span>\n <ng-content></ng-content>\n</div>\n", styles: [".fudis-select-group__label{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:300;color:#727272;box-sizing:border-box;display:flex;align-items:center;border-top:1px solid #d4d4d4;padding-left:calc(.5rem / var(--fudis-rem-multiplier));min-height:calc(2rem / var(--fudis-rem-multiplier))}.fudis-select-group--hidden{display:none;visibility:hidden}\n"] }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: SelectComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: MultiselectComponent, decorators: [{
type: Host
}, {
type: Optional
}] }], propDecorators: { label: [{
type: Input,
args: [{ required: true }]
}] } });
class SelectOptionBaseDirective extends DropdownItemBaseDirective {
constructor(_document, _parentGroup, _translationService, _idService) {
super(_document);
this._parentGroup = _parentGroup;
this._translationService = _translationService;
this._idService = _idService;
/**
* State of option visibility
*/
this._optionVisible = signal(true, ...(ngDevMode ? [{ debugName: "_optionVisible" }] : /* istanbul ignore next */ []));
/**
* Focus state
*/
this._focused = false;
/**
* Selected state of an option
*/
this._selected = signal(false, ...(ngDevMode ? [{ debugName: "_selected" }] : /* istanbul ignore next */ []));
}
/**
* Get visibility status of this option
*/
get visible() {
return this._optionVisible();
}
/**
* User focus handler
*/
_focus() {
this._focused = true;
this._parent.setFocusedOption(this._id, 'add');
}
/**
* For autocompletes, compare if current filter text is contained in this option's label. If not,
* hide the option from the dropdown list.
*
* @param filterText Autocomplete filter text from parent
*/
_isOptionVisible(filterText) {
if (this.data) {
const labelMatch = filterText && this.data.label.toLowerCase().includes(filterText.toLowerCase())
? true
: !filterText;
const subLabelMatch = filterText && this.data.subLabel?.toLowerCase().includes(filterText.toLowerCase())
? true
: !filterText;
this._optionVisible.set(labelMatch || subLabelMatch);
this._updateVisibilityToParents(this._optionVisible());
}
}
/**
* Update option visibility to parent component
*/
_updateVisibilityToParents(visible) {
this._parent.setOptionVisibility(this._id, visible);
if (this._parentGroup) {
this._parentGroup.setOptionVisibility(this._id, visible);
}
}
/**
* Handler for keydown keyboard events
*
* @param event Keyboard event
*/
_keyDown(event) {
event.preventDefault();
if ((event.key === 'Enter' || event.key === ' ') && !this.data.disabled) {
this._clickOption(event);
}
else if (event.key === 'Tab') {
this._parent.focusToInput();
this._parent.closeDropdown();
}
else if (event.key !== ' ') {
this._baseHandleKeyDown(event, this._optionInputRef, this._parent.focusSelector);
}
}
/**
* Handler for blurring out from focused option
*
* @param event FocusEvent to emit
*/
_blur(event) {
this._focused = false;
this._parent.setFocusedOption(this._id, 'remove');
this.handleBlur.emit(event);
this._parent.componentFocused(event).then((value) => {
if (!value) {
this._parent.closeDropdown(false);
}
});
}
/**
* Select and Multiselect shared logic for subscribing to parent FormControl's events and syncing
* selected state of options.
*/
_initSelectedStateSync() {
toObservable(this._parent.getControlRef())
.pipe(
// Necessary for supporting dynamic FormControl assignment to the parent select component.
// If the control changes, we want to unsubscribe from the previous one and subscribe to the new one.
switchMap((control) => {
if (!control)
return EMPTY;
return control.events.pipe(startWith(null));
}), takeUntilDestroyed())
.subscribe(() => {
this._syncSelectedState();
});
}
_syncSelectedState() {
if (!this.data) {
this._selected.set(false);
return;
}
this._selected.set(this._isSelected());
}
/**
* Every subclass (i.e. SelectOption and MultiselectOption) needs to implement its own logic to
* determine if it is selected or not, based on the parent select's control value
*/
_isSelected() {
throw new Error('_isSelected must be implemented in a child class');
}
/**
* Boilerplate function to be overriden by SelectOption's and MultiselectOption's own
* implementations
*/
// eslint-disable-next-line
_checkVisibilityFromFilterText(filterText) { }
/**
* Boilerplate function to be overriden by SelectOption's and MultiselectOption's own
* implementations
*/
// eslint-disable-next-line
_clickOption(event) { }
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectOptionBaseDirective, deps: [{ token: DOCUMENT }, { token: SelectGroupComponent, host: true, optional: true }, { token: FudisTranslationService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SelectOptionBaseDirective, isStandalone: true, selector: "[fudisSelectOptionBase]", inputs: { data: "data" }, viewQueries: [{ propertyName: "_optionInputRef", first: true, predicate: ["optionInputRef"], descendants: true }], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectOptionBaseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisSelectOptionBase]' }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: SelectGroupComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: FudisTranslationService }, { type: FudisIdService }], propDecorators: { _optionInputRef: [{
type: ViewChild,
args: ['optionInputRef']
}], data: [{
type: Input,
args: [{ required: true }]
}] } });
/**
* Represents a selectable option within SelectComponent.
*/
class SelectOptionComponent extends SelectOptionBaseDirective {
constructor(_document, _parentSelect, _parentGroup, _translationService, _idService) {
super(_document, _parentGroup, _translationService, _idService);
this._parentSelect = _parentSelect;
this._parent = _parentSelect;
this._initSelectedStateSync();
toObservable(this._parent.getAutocompleteFilterText())
.pipe(takeUntilDestroyed())
.subscribe((filterText) => {
this._checkVisibilityFromFilterText(filterText);
});
}
ngOnChanges(changes) {
if (changes.data?.currentValue !== changes.data?.previousValue) {
this._syncSelectedState();
const newOptionId = this._idService.getNewSelectOptionId(this.data.label, 'select', this._parent.id, this._parentGroup?.id);
if (this._id && this._id !== newOptionId)
this.clearParentOptionVisibility();
this._id = newOptionId;
this._checkVisibilityFromFilterText(this._parent.getAutocompleteFilterText()());
if (changes.data?.currentValue && changes.data.currentValue !== changes.data.previousValue) {
this._checkIfLabelRequiresUpdate(changes.data.currentValue);
}
}
}
ngOnDestroy() {
this.clearParentOptionVisibility();
}
clearParentOptionVisibility() {
this._parentSelect.setOptionVisibility(this._id, false);
this._parentGroup?.setOptionVisibility(this._id, false);
}
/**
* Click handler for Select Option click
*
* @param event Event emitted
*/
_clickOption(event) {
if (!this.data.disabled) {
this._parentSelect.handleSelectionChange(this.data);
this.handleClick.emit(event);
}
this._parent.closeDropdown(true, true);
}
_checkVisibilityFromFilterText(filterText) {
if (this._parent.variant !== 'dropdown' && this._parent.autocompleteFilter) {
this._isOptionVisible(filterText);
this._isOptionTyped(filterText);
}
else {
this._isOptionVisible('');
}
}
/**
* Checks if this option is currently selected in the parent select.
*/
_isSelected() {
return this._parent.control.value?.value === this.data.value;
}
/**
* When option is changed, it will not change Form Control's value, which is intended, but visible
* label should be updated
*/
_checkIfLabelRequiresUpdate(newData) {
const controlValue = this._parent?.control.value;
if (controlValue?.value === newData.value) {
this._parent.selectCVA?.writeValue(newData);
this._parent.setAutocompleteFilterText(newData.label, false);
}
}
/**
* Used with autocomplete to check if filter text matches this option. If yes, then trigger
* selection change in the parent
*
* @param filterText Autocomplete filter text from parent
*/
_isOptionTyped(filterText) {
if (!this.data?.disabled && this.data?.label?.toLowerCase() === filterText?.toLowerCase()) {
if (this._parent.control.value?.value !== this.data.value) {
this._parentSelect.handleSelectionChange(this.data);
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectOptionComponent, deps: [{ token: DOCUMENT }, { token: SelectComponent, host: true }, { token: SelectGroupComponent, host: true, optional: true }, { token: FudisTranslationService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SelectOptionComponent, isStandalone: true, selector: "fudis-select-option", usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<!--\nSingleselect option\n-->\n<div\n #optionInputRef\n class=\"fudis-select-option\"\n [class.fudis-select-option__focusable]=\"_optionVisible()\"\n [class.fudis-select-option__xs]=\"_parent.size === 'xs'\"\n [class.fudis-select-option--disabled]=\"data.disabled\"\n [class.fudis-select-option--selected]=\"_selected()\"\n [attr.aria-selected]=\"_selected()\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.id]=\"_id\"\n [attr.aria-disabled]=\"data.disabled || null\"\n (focus)=\"_focus()\"\n (keydown)=\"_keyDown($event)\"\n (blur)=\"_blur($event)\"\n (click)=\"!data.disabled && _clickOption($event)\"\n>\n <div class=\"fudis-select-option__label\">\n <span class=\"fudis-select-option__label__main\">{{ data.label }}</span>\n @if (data.subLabel) {\n <span class=\"fudis-select-option__label__sub\">{{ data.subLabel }}</span>\n }\n </div>\n @if (!data.disabled && _selected()) {\n <fudis-icon [icon]=\"_parent.size === 'xs' ? 'check-small' : 'check'\" [color]=\"'gray-dark'\" />\n }\n @if (data.disabled) {\n <span class=\"fudis-select-option__label fudis-select-option__disabled-text\">\n ({{ _translationService.getTranslations()().SELECT.DISABLED }})</span\n >\n }\n</div>\n", styles: [".fudis-select-option{box-sizing:border-box;margin:0;padding:0;text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400;background-color:transparent;color:#484848;border:none;display:flex;position:relative;align-items:flex-start;justify-content:space-between;padding-left:calc(1rem / var(--fudis-rem-multiplier));width:100%;min-height:calc(2rem / var(--fudis-rem-multiplier));overflow:hidden;text-align:left;cursor:pointer}.fudis-select-option:not(.fudis-select-option__focusable){display:none;visibility:hidden}.fudis-select-option:not([aria-disabled=true]):hover{background-color:var(--fudis-color-primary-light)}.fudis-select-option:not([aria-disabled=true]):focus-visible,.fudis-select-option:not([aria-disabled=true]):focus{background-color:var(--fudis-color-primary-light);outline:2px dashed #484848;outline-offset:-2px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-select-option:not([aria-disabled=true]):active{background-color:var(--fudis-color-primary);color:#fff}.fudis-select-option--disabled{background-color:#f1f1f1;color:#727272;cursor:default}.fudis-select-option__xs{align-items:center;padding:calc(.25rem / var(--fudis-rem-multiplier))}.fudis-select-option__focusable,.fudis-select-option--visible{display:flex;visibility:visible}.fudis-select-option__disabled-text{padding-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-select-option__label{margin:calc(.5rem / var(--fudis-rem-multiplier)) 0;line-height:calc(1rem / var(--fudis-rem-multiplier))}.fudis-select-option__label__main{display:block}.fudis-select-option__label__sub{text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400;display:block;margin-top:calc(.25rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectOptionComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-select-option', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent], template: "<!--\nSingleselect option\n-->\n<div\n #optionInputRef\n class=\"fudis-select-option\"\n [class.fudis-select-option__focusable]=\"_optionVisible()\"\n [class.fudis-select-option__xs]=\"_parent.size === 'xs'\"\n [class.fudis-select-option--disabled]=\"data.disabled\"\n [class.fudis-select-option--selected]=\"_selected()\"\n [attr.aria-selected]=\"_selected()\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.id]=\"_id\"\n [attr.aria-disabled]=\"data.disabled || null\"\n (focus)=\"_focus()\"\n (keydown)=\"_keyDown($event)\"\n (blur)=\"_blur($event)\"\n (click)=\"!data.disabled && _clickOption($event)\"\n>\n <div class=\"fudis-select-option__label\">\n <span class=\"fudis-select-option__label__main\">{{ data.label }}</span>\n @if (data.subLabel) {\n <span class=\"fudis-select-option__label__sub\">{{ data.subLabel }}</span>\n }\n </div>\n @if (!data.disabled && _selected()) {\n <fudis-icon [icon]=\"_parent.size === 'xs' ? 'check-small' : 'check'\" [color]=\"'gray-dark'\" />\n }\n @if (data.disabled) {\n <span class=\"fudis-select-option__label fudis-select-option__disabled-text\">\n ({{ _translationService.getTranslations()().SELECT.DISABLED }})</span\n >\n }\n</div>\n", styles: [".fudis-select-option{box-sizing:border-box;margin:0;padding:0;text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400;background-color:transparent;color:#484848;border:none;display:flex;position:relative;align-items:flex-start;justify-content:space-between;padding-left:calc(1rem / var(--fudis-rem-multiplier));width:100%;min-height:calc(2rem / var(--fudis-rem-multiplier));overflow:hidden;text-align:left;cursor:pointer}.fudis-select-option:not(.fudis-select-option__focusable){display:none;visibility:hidden}.fudis-select-option:not([aria-disabled=true]):hover{background-color:var(--fudis-color-primary-light)}.fudis-select-option:not([aria-disabled=true]):focus-visible,.fudis-select-option:not([aria-disabled=true]):focus{background-color:var(--fudis-color-primary-light);outline:2px dashed #484848;outline-offset:-2px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-select-option:not([aria-disabled=true]):active{background-color:var(--fudis-color-primary);color:#fff}.fudis-select-option--disabled{background-color:#f1f1f1;color:#727272;cursor:default}.fudis-select-option__xs{align-items:center;padding:calc(.25rem / var(--fudis-rem-multiplier))}.fudis-select-option__focusable,.fudis-select-option--visible{display:flex;visibility:visible}.fudis-select-option__disabled-text{padding-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-select-option__label{margin:calc(.5rem / var(--fudis-rem-multiplier)) 0;line-height:calc(1rem / var(--fudis-rem-multiplier))}.fudis-select-option__label__main{display:block}.fudis-select-option__label__sub{text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400;display:block;margin-top:calc(.25rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: SelectComponent, decorators: [{
type: Host
}] }, { type: SelectGroupComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: FudisTranslationService }, { type: FudisIdService }] });
/**
* Manages localized text inputs across multiple languages.
*
* Use this component to edit equivalent content for different language options.
*/
class LocalizedTextGroupComponent extends GroupComponentBaseDirective {
constructor(_translationService, _idService, _focusService) {
super(_idService, _focusService);
this._translationService = _translationService;
/**
* Option list for language Selection. To pair controls with corresponding Select option,
* FormControl's name must match with the controlName defined here. E.g. by default "{controlName:
* 'en', label: 'EN'}" pairs with Form Group's "en: new FormControl('')"
*/
this.options = [
{ controlName: 'fi', label: 'FI' },
{ controlName: 'sv', label: 'SV' },
{ controlName: 'en', label: 'EN' },
];
/**
* Total width of the component, including both TextInput and Select.
*/
this.size = 'lg';
/**
* If user clears the input field, set FormControl value to null instead of empty string.
*/
this.nullControlOnEmptyString = true;
/**
* Form element to display. Defaults to text-input
*/
this.variant = 'text-input';
/**
* Min length for HTML attribute and for character indicator in guidance
*/
this._minLength = new BehaviorSubject(null);
/**
* Max length for HTML attribute and for character indicator in guidance
*/
this._maxLength = new BehaviorSubject(null);
/**
* Control for language option Select
*/
this._selectControl = new FormControl(null);
/**
* Updated options list after changes. Option values (i.e. controlName) are always strings in this
* component so we can type them as such.
*/
this._selectOptions = [];
/**
* Fudis translation
*/
this._languageLabel = new BehaviorSubject(this._translationService.getTranslations()().INPUT_WITH_LANGUAGE_OPTIONS.LANGUAGE);
effect(() => {
const translations = _translationService.getTranslations()();
this._languageLabel.next(translations.INPUT_WITH_LANGUAGE_OPTIONS.LANGUAGE);
this._missingLanguage = translations.INPUT_WITH_LANGUAGE_OPTIONS.MISSING;
if (this.options) {
this._updateSelectOptions();
}
});
this._updateValueAndValidityTrigger.pipe(takeUntilDestroyed()).subscribe(() => {
if (this.formGroup && this._selectControl.value) {
this._checkHtmlAttributes(this._selectControl.value.value);
this._updateSelectOptions();
}
});
}
/**
* When Form Control value changes, update Select Options accordingly with or without Missing text
*/
_updateSelectOptions() {
const newOptions = [];
this.options.forEach((option) => {
let newOption = null;
if (this.formGroup.controls[option.controlName].invalid ||
!this.formGroup.controls[option.controlName].value) {
newOption = {
value: option.controlName,
label: `${option.label} (${this._missingLanguage})`,
};
}
else {
newOption = { value: option.controlName, label: option.label };
}
newOptions.push(newOption);
if (option.controlName === this._selectControl?.value?.value) {
this._selectControl.patchValue(newOption);
}
});
this._selectOptions = newOptions;
}
/**
* Set subscriptions for each control to set value to null if empty string is entered.
*/
_setControlsValueSubscription() {
if (this.nullControlOnEmptyString) {
this._controlsSubscription?.unsubscribe();
this._controlsSubscription = new Subscription();
Object.keys(this.formGroup.controls).forEach((key) => {
const control = this.formGroup.controls[key];
this._controlsSubscription.add(control.valueChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe((value) => {
if (typeof value === 'string' && value.trim() === '') {
control.setValue(null);
}
}));
});
}
}
/**
* On init and when Select option changes, check if now visible input should be marked as
* required.
*/
_isInputRequired(control) {
const groupRequiredError = this.formGroup?.errors?.['oneRequired'];
const controlRequiredValidator = FudisValidatorUtilities.required(control);
const groupRequiredValidator = FudisValidatorUtilities.oneRequiredOrMin(this.formGroup);
const nonEmptyControls = Object.keys(this.formGroup.controls).filter((control) => {
return this.formGroup.controls[control].value;
});
if (controlRequiredValidator ||
groupRequiredError ||
(nonEmptyControls.length === 1 && control.value && groupRequiredValidator)) {
return true;
}
return false;
}
_checkHtmlAttributes(controlName) {
if (controlName) {
const control = this.formGroup.controls[controlName];
this._minLength.next(FudisValidatorUtilities.minLength(control));
this._maxLength.next(FudisValidatorUtilities.maxLength(control));
this._required.next(this._isInputRequired(control));
}
}
ngOnInit() {
this._setComponentId('localized-text-group');
}
ngOnChanges(changes) {
if (changes.formGroup?.currentValue !== changes.formGroup?.previousValue) {
this._subscription?.unsubscribe();
this._subscription = this.formGroup.valueChanges
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => this._updateValueAndValidityTrigger.next());
this._updateSelectOptions();
this._setControlsValueSubscription();
this._selectControl.patchValue(this._selectOptions[0]);
this._checkHtmlAttributes(this._selectOptions[0].value);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LocalizedTextGroupComponent, deps: [{ token: FudisTranslationService }, { token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: LocalizedTextGroupComponent, isStandalone: true, selector: "fudis-localized-text-group", inputs: { formGroup: "formGroup", options: "options", size: "size", nullControlOnEmptyString: "nullControlOnEmptyString", variant: "variant" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "@if (_selectControl.value) {\n <p [attr.id]=\"id + '-group-label'\" class=\"fudis-visually-hidden\">{{ label }}</p>\n}\n@if (_selectControl.value) {\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"id + '-group-label'\"\n class=\"fudis-localized-text-group fudis-input-size__{{ size }}\"\n >\n <div class=\"fudis-localized-text-group__inputs\">\n <fudis-label\n [for]=\"id\"\n [text]=\"label\"\n [id]=\"id + '-label'\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n />\n @for (control of formGroup.controls | keyvalue; track control.key; let index = $index) {\n @if (variant === \"text-input\" && control.key === _selectControl.value.value) {\n <input\n #inputRef\n class=\"fudis-form-input fudis-localized-text-group__inputs__text\"\n [class.fudis-form-input--touched]=\"\n $any(formGroup).controls[control.key].touched || formGroup.touched\n \"\n [attr.aria-invalid]=\"(formGroup.invalid && formGroup.touched) || null\"\n [attr.aria-label]=\"\n ariaLabel\n ? label + ', ' + _selectControl.value.label + ariaLabel\n : label + ', ' + _selectControl.value.label\n \"\n [attr.minlength]=\"_minLength | async\"\n [attr.maxlength]=\"_maxLength | async\"\n [formControl]=\"$any(formGroup).controls[control.key]\"\n (keyup)=\"_updateSelectOptions(); handleKeyUp.emit($event)\"\n (blur)=\"handleBlur.emit($event)\"\n (focus)=\"onFocus($event)\"\n [id]=\"id\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-disabled]=\"\n formGroup.disabled ||\n $any(formGroup).controls[control.key].disabled ||\n disabled ||\n null\n \"\n [readonly]=\"\n formGroup.disabled ||\n $any(formGroup).controls[control.key].disabled ||\n disabled ||\n null\n \"\n />\n }\n @if (variant === \"text-area\" && control.key === _selectControl.value.value) {\n <textarea\n #inputRef\n class=\"fudis-form-input fudis-localized-text-group__inputs__area\"\n [class.fudis-form-input--touched]=\"\n $any(formGroup).controls[control.key].touched || formGroup.touched\n \"\n [attr.aria-invalid]=\"(formGroup.invalid && formGroup.touched) || null\"\n [attr.aria-label]=\"\n ariaLabel\n ? label + ', ' + _selectControl.value.label + ariaLabel\n : label + ', ' + _selectControl.value.label\n \"\n [attr.minlength]=\"_minLength | async\"\n [attr.maxlength]=\"_maxLength | async\"\n [formControl]=\"$any(formGroup).controls[control.key]\"\n (keyup)=\"_updateSelectOptions(); handleKeyUp.emit($event)\"\n (blur)=\"handleBlur.emit($event)\"\n (focus)=\"onFocus($event)\"\n [id]=\"id\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-disabled]=\"\n $any(formGroup).controls[control.key].disabled || disabled || null\n \"\n [readonly]=\"$any(formGroup).controls[control.key].disabled || disabled || null\"\n >\n </textarea>\n }\n }\n @if (!disableGuidance) {\n <fudis-guidance\n [inputLabel]=\"label\"\n [maxLength]=\"_maxLength | async\"\n [helpText]=\"helpText\"\n [formGroup]=\"formGroup\"\n [for]=\"id\"\n [selectedOption]=\"_selectControl.value.value\"\n >\n <ng-content select=\"fudis-error-message\"\n /></fudis-guidance>\n }\n </div>\n @if (_languageLabel | async; as label) {\n <fudis-select\n class=\"fudis-localized-text-group__select\"\n [disabled]=\"this.formGroup.disabled || disabled\"\n [label]=\"label\"\n [selectionClearButton]=\"false\"\n (selectionUpdate)=\"_checkHtmlAttributes($event?.value)\"\n [control]=\"_selectControl\"\n [size]=\"'xs'\"\n [id]=\"id + '_language-select'\"\n >\n <ng-template fudisSelectOptions>\n @for (option of _selectOptions; track option.value) {\n <fudis-select-option\n [data]=\"{ value: option.value, label: option.label, disabled: option.disabled }\"\n />\n }\n </ng-template>\n </fudis-select>\n }\n </div>\n}\n", styles: [".fudis-localized-text-group{box-sizing:border-box;margin:0;padding:0;display:flex;align-items:baseline}.fudis-localized-text-group__inputs{margin-right:calc(.25rem / var(--fudis-rem-multiplier));width:100%}.fudis-localized-text-group__inputs__text{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;border-radius:2px;box-sizing:border-box;padding:calc(.25rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));width:100%;height:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-localized-text-group__inputs__text:focus{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-localized-text-group__inputs__area{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;box-sizing:border-box;display:block;padding:calc(.25rem / var(--fudis-rem-multiplier));width:100%;height:calc(5rem / var(--fudis-rem-multiplier));resize:vertical}.fudis-localized-text-group__inputs__area:focus{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-localized-text-group__select{min-width:calc(5.75rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "component", type: LabelComponent, selector: "fudis-label", inputs: ["id", "text", "for", "required"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i3$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: GuidanceComponent, selector: "fudis-guidance", inputs: ["for", "inputLabel", "control", "formGroup", "helpText", "maxLength", "selectedOption", "groupBlurredOut", "groupHelpTextHidden"] }, { kind: "component", type: SelectComponent, selector: "fudis-select", inputs: ["control"], outputs: ["selectionUpdate"] }, { kind: "directive", type: SelectOptionsDirective, selector: "[fudisSelectOptions]" }, { kind: "component", type: SelectOptionComponent, selector: "fudis-select-option" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: LocalizedTextGroupComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-localized-text-group', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
LabelComponent,
FormsModule,
ReactiveFormsModule,
GuidanceComponent,
SelectComponent,
SelectOptionsDirective,
SelectOptionComponent,
AsyncPipe,
KeyValuePipe,
], template: "@if (_selectControl.value) {\n <p [attr.id]=\"id + '-group-label'\" class=\"fudis-visually-hidden\">{{ label }}</p>\n}\n@if (_selectControl.value) {\n <div\n role=\"group\"\n [attr.aria-labelledby]=\"id + '-group-label'\"\n class=\"fudis-localized-text-group fudis-input-size__{{ size }}\"\n >\n <div class=\"fudis-localized-text-group__inputs\">\n <fudis-label\n [for]=\"id\"\n [text]=\"label\"\n [id]=\"id + '-label'\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n />\n @for (control of formGroup.controls | keyvalue; track control.key; let index = $index) {\n @if (variant === \"text-input\" && control.key === _selectControl.value.value) {\n <input\n #inputRef\n class=\"fudis-form-input fudis-localized-text-group__inputs__text\"\n [class.fudis-form-input--touched]=\"\n $any(formGroup).controls[control.key].touched || formGroup.touched\n \"\n [attr.aria-invalid]=\"(formGroup.invalid && formGroup.touched) || null\"\n [attr.aria-label]=\"\n ariaLabel\n ? label + ', ' + _selectControl.value.label + ariaLabel\n : label + ', ' + _selectControl.value.label\n \"\n [attr.minlength]=\"_minLength | async\"\n [attr.maxlength]=\"_maxLength | async\"\n [formControl]=\"$any(formGroup).controls[control.key]\"\n (keyup)=\"_updateSelectOptions(); handleKeyUp.emit($event)\"\n (blur)=\"handleBlur.emit($event)\"\n (focus)=\"onFocus($event)\"\n [id]=\"id\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-disabled]=\"\n formGroup.disabled ||\n $any(formGroup).controls[control.key].disabled ||\n disabled ||\n null\n \"\n [readonly]=\"\n formGroup.disabled ||\n $any(formGroup).controls[control.key].disabled ||\n disabled ||\n null\n \"\n />\n }\n @if (variant === \"text-area\" && control.key === _selectControl.value.value) {\n <textarea\n #inputRef\n class=\"fudis-form-input fudis-localized-text-group__inputs__area\"\n [class.fudis-form-input--touched]=\"\n $any(formGroup).controls[control.key].touched || formGroup.touched\n \"\n [attr.aria-invalid]=\"(formGroup.invalid && formGroup.touched) || null\"\n [attr.aria-label]=\"\n ariaLabel\n ? label + ', ' + _selectControl.value.label + ariaLabel\n : label + ', ' + _selectControl.value.label\n \"\n [attr.minlength]=\"_minLength | async\"\n [attr.maxlength]=\"_maxLength | async\"\n [formControl]=\"$any(formGroup).controls[control.key]\"\n (keyup)=\"_updateSelectOptions(); handleKeyUp.emit($event)\"\n (blur)=\"handleBlur.emit($event)\"\n (focus)=\"onFocus($event)\"\n [id]=\"id\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-disabled]=\"\n $any(formGroup).controls[control.key].disabled || disabled || null\n \"\n [readonly]=\"$any(formGroup).controls[control.key].disabled || disabled || null\"\n >\n </textarea>\n }\n }\n @if (!disableGuidance) {\n <fudis-guidance\n [inputLabel]=\"label\"\n [maxLength]=\"_maxLength | async\"\n [helpText]=\"helpText\"\n [formGroup]=\"formGroup\"\n [for]=\"id\"\n [selectedOption]=\"_selectControl.value.value\"\n >\n <ng-content select=\"fudis-error-message\"\n /></fudis-guidance>\n }\n </div>\n @if (_languageLabel | async; as label) {\n <fudis-select\n class=\"fudis-localized-text-group__select\"\n [disabled]=\"this.formGroup.disabled || disabled\"\n [label]=\"label\"\n [selectionClearButton]=\"false\"\n (selectionUpdate)=\"_checkHtmlAttributes($event?.value)\"\n [control]=\"_selectControl\"\n [size]=\"'xs'\"\n [id]=\"id + '_language-select'\"\n >\n <ng-template fudisSelectOptions>\n @for (option of _selectOptions; track option.value) {\n <fudis-select-option\n [data]=\"{ value: option.value, label: option.label, disabled: option.disabled }\"\n />\n }\n </ng-template>\n </fudis-select>\n }\n </div>\n}\n", styles: [".fudis-localized-text-group{box-sizing:border-box;margin:0;padding:0;display:flex;align-items:baseline}.fudis-localized-text-group__inputs{margin-right:calc(.25rem / var(--fudis-rem-multiplier));width:100%}.fudis-localized-text-group__inputs__text{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;border-radius:2px;box-sizing:border-box;padding:calc(.25rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));width:100%;height:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-localized-text-group__inputs__text:focus{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-localized-text-group__inputs__area{line-height:calc(1.25rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(1rem / var(--fudis-rem-multiplier));font-weight:300;box-sizing:border-box;display:block;padding:calc(.25rem / var(--fudis-rem-multiplier));width:100%;height:calc(5rem / var(--fudis-rem-multiplier));resize:vertical}.fudis-localized-text-group__inputs__area:focus{border:1px solid #1076db;outline:2px solid #1076db;outline-offset:-2px;box-shadow:0 0 0 1px #fff}.fudis-localized-text-group__select{min-width:calc(5.75rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: FudisIdService }, { type: FudisFocusService }], propDecorators: { formGroup: [{
type: Input,
args: [{ required: true }]
}], options: [{
type: Input
}], size: [{
type: Input
}], nullControlOnEmptyString: [{
type: Input
}], variant: [{
type: Input
}] } });
/**
* Represents a selectable option within MultiselectComponent.
*/
class MultiselectOptionComponent extends SelectOptionBaseDirective {
static { this.instanceCounter = 0; }
constructor(_document, _parentMultiselect, _parentGroup, _idService, _translationService) {
super(_document, _parentGroup, _translationService, _idService);
this._parentMultiselect = _parentMultiselect;
MultiselectOptionComponent.instanceCounter++;
this.componentInstanceId = MultiselectOptionComponent.instanceCounter.toString();
this._parent = this._parentMultiselect;
this._initSelectedStateSync();
toObservable(this._parent.getAutocompleteFilterText())
.pipe(takeUntilDestroyed())
.subscribe((filterText) => {
this._checkVisibilityFromFilterText(filterText);
});
}
ngOnChanges(changes) {
if (changes.data?.currentValue !== changes.data?.previousValue) {
this._syncSelectedState();
const newOptionId = this._idService.getNewSelectOptionId(this.data.label, 'multiselect', this._parent.id, this._parentGroup?.id);
if (this._id && this._id !== newOptionId)
this.clearParentOptionVisibility();
this._id = newOptionId;
this._checkVisibilityFromFilterText(this._parent.getAutocompleteFilterText()());
if (changes.data?.currentValue && changes.data.currentValue !== changes.data.previousValue) {
this._checkIfLabelRequiresUpdate(changes.data?.currentValue);
}
}
}
ngOnDestroy() {
this.clearParentOptionVisibility();
}
clearParentOptionVisibility() {
this._parentMultiselect.setOptionVisibility(this._id, false);
this._parentGroup?.setOptionVisibility(this._id, false);
}
/**
* Click handler for clicking the option
*
* @param event Event
*/
_clickOption(event) {
if (!this.data.disabled) {
if (!this._selected()) {
this._parentMultiselect.handleMultiSelectionChange(this.data, 'add');
}
else {
this._parentMultiselect.handleMultiSelectionChange(this.data, 'remove');
}
this.handleClick.emit(event);
this.handleChecked.emit();
}
}
_checkVisibilityFromFilterText(filterText) {
if (this._parent.variant !== 'dropdown' && this._parent.autocompleteFilter) {
this._isOptionVisible(filterText);
}
else {
this._isOptionVisible('');
}
}
/**
* Checks if this option is currently selected in the parent multiselect and if the option's value
* exists in the parent FormControl's value array.
*/
_isSelected() {
return !!this._parent.control.value?.find((option) => option.value === this.data.value);
}
/**
* When option is changed, it will not change Form Control's value, which is intended, but visible
* label should be updated
*/
_checkIfLabelRequiresUpdate(newData) {
const controlValue = this._parent?.control.value;
const foundMatch = controlValue?.find((selectedOption) => {
return selectedOption.value === newData.value;
});
if (foundMatch) {
this._parent.selectedOptionsFromLangChange.push(newData);
if (this._parent.selectedOptionsFromLangChange.length === this._parent.control.value?.length) {
this._parent._multiselectCVA?.writeValue(this._parent.selectedOptionsFromLangChange);
this._parent.selectedOptionsFromLangChange = [];
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectOptionComponent, deps: [{ token: DOCUMENT }, { token: MultiselectComponent, host: true }, { token: SelectGroupComponent, host: true, optional: true }, { token: FudisIdService }, { token: FudisTranslationService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MultiselectOptionComponent, isStandalone: true, selector: "fudis-multiselect-option", usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"fudis-multiselect-option\"\n [attr.id]=\"_id\"\n tabindex=\"-1\"\n [class.fudis-multiselect-option--visible]=\"_optionVisible()\"\n [class.fudis-multiselect-option--checked]=\"_selected()\"\n [class.fudis-multiselect-option--focused]=\"_focused\"\n [class.fudis-multiselect-option--disabled]=\"data.disabled\"\n>\n <label\n [attr.for]=\"_id + '-checkbox-input-' + componentInstanceId\"\n class=\"fudis-multiselect-option__label\"\n [class.fudis-multiselect-option__label--focus]=\"_focused\"\n [class.fudis-multiselect-option__label--checked]=\"_selected()\"\n >\n <div\n class=\"fudis-multiselect-option__label__checkbox\"\n [class.fudis-multiselect-option__label__checkbox--disabled]=\"data.disabled\"\n >\n <input\n #optionInputRef\n [class.fudis-select-option__focusable]=\"_optionVisible()\"\n type=\"checkbox\"\n class=\"fudis-multiselect-option__label__checkbox__input\"\n role=\"option\"\n [attr.id]=\"_id + '-checkbox-input-' + componentInstanceId\"\n [attr.aria-disabled]=\"data.disabled || null\"\n (click)=\"!data.disabled && _clickOption($event)\"\n [attr.aria-selected]=\"_selected()\"\n (focus)=\"_focus()\"\n (keydown)=\"_keyDown($event)\"\n (blur)=\"_blur($event)\"\n />\n @if (_selected()) {\n <fudis-icon class=\"fudis-multiselect-option__label__checkbox__icon\" [icon]=\"'check'\" />\n }\n </div>\n <div class=\"fudis-multiselect-option__label__text\">\n <span class=\"fudis-multiselect-option__label__text__main\">{{ data.label }}</span>\n @if (data.subLabel) {\n <span class=\"fudis-multiselect-option__label__text__sub\">{{ data.subLabel }}</span>\n }\n\n @if (data.disabled) {\n <span class=\"fudis-multiselect-option__label__text__disabled\">\n ({{ _translationService.getTranslations()().SELECT.DISABLED }})</span\n >\n }\n </div>\n </label>\n</div>\n", styles: [".fudis-multiselect-option{color:#484848;width:auto}.fudis-multiselect-option:not(.fudis-multiselect-option--visible){display:none;visibility:hidden}.fudis-multiselect-option:hover:not(:has(.fudis-multiselect-option__label__checkbox__input[aria-disabled=true])){background-color:var(--fudis-color-primary-light)}.fudis-multiselect-option__disabled-text{padding-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-multiselect-option__label{text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400;display:flex;align-items:flex-start;margin-bottom:0;padding:calc(.5rem / var(--fudis-rem-multiplier)) calc(.5rem / var(--fudis-rem-multiplier)) calc(.5rem / var(--fudis-rem-multiplier)) 0}.fudis-multiselect-option__label__checkbox{border:1px solid #727272;background-color:#fff;display:flex;position:absolute;align-items:center;justify-content:center;margin-left:calc(.5rem / var(--fudis-rem-multiplier));border-radius:0;width:calc(1rem / var(--fudis-rem-multiplier));height:calc(1rem / var(--fudis-rem-multiplier));cursor:pointer}.fudis-multiselect-option__label__checkbox--disabled{border:1px dashed #727272;background-color:#f1f1f1;cursor:default}.fudis-multiselect-option__label__checkbox__icon{transform:scale(.85)}.fudis-multiselect-option__label__checkbox__input{box-sizing:border-box;margin:0;padding:0;appearance:none;display:grid;position:absolute;place-content:center}.fudis-multiselect-option__label__checkbox__input:not([aria-disabled=true]):focus{outline:2px solid var(--fudis-color-primary);outline-offset:-2px;outline-offset:-1px;width:calc(1.25rem / var(--fudis-rem-multiplier));height:calc(1.25rem / var(--fudis-rem-multiplier))}.fudis-multiselect-option__label__text{position:relative;margin-left:calc(2rem / var(--fudis-rem-multiplier))}.fudis-multiselect-option__label__text__main{display:block}.fudis-multiselect-option__label__text__sub{text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400;display:block;margin-top:2px;margin-bottom:2px}.fudis-multiselect-option__label__text__disabled{display:block}.fudis-multiselect-option--checked{color:#fff;background-color:var(--fudis-color-primary)}.fudis-multiselect-option--checked:hover{color:#484848}.fudis-multiselect-option--disabled{background-color:#f1f1f1;color:#727272;cursor:default}.fudis-multiselect-option--focused{background-color:var(--fudis-color-primary-light);color:#484848}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MultiselectOptionComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-multiselect-option', changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent], template: "<div\n class=\"fudis-multiselect-option\"\n [attr.id]=\"_id\"\n tabindex=\"-1\"\n [class.fudis-multiselect-option--visible]=\"_optionVisible()\"\n [class.fudis-multiselect-option--checked]=\"_selected()\"\n [class.fudis-multiselect-option--focused]=\"_focused\"\n [class.fudis-multiselect-option--disabled]=\"data.disabled\"\n>\n <label\n [attr.for]=\"_id + '-checkbox-input-' + componentInstanceId\"\n class=\"fudis-multiselect-option__label\"\n [class.fudis-multiselect-option__label--focus]=\"_focused\"\n [class.fudis-multiselect-option__label--checked]=\"_selected()\"\n >\n <div\n class=\"fudis-multiselect-option__label__checkbox\"\n [class.fudis-multiselect-option__label__checkbox--disabled]=\"data.disabled\"\n >\n <input\n #optionInputRef\n [class.fudis-select-option__focusable]=\"_optionVisible()\"\n type=\"checkbox\"\n class=\"fudis-multiselect-option__label__checkbox__input\"\n role=\"option\"\n [attr.id]=\"_id + '-checkbox-input-' + componentInstanceId\"\n [attr.aria-disabled]=\"data.disabled || null\"\n (click)=\"!data.disabled && _clickOption($event)\"\n [attr.aria-selected]=\"_selected()\"\n (focus)=\"_focus()\"\n (keydown)=\"_keyDown($event)\"\n (blur)=\"_blur($event)\"\n />\n @if (_selected()) {\n <fudis-icon class=\"fudis-multiselect-option__label__checkbox__icon\" [icon]=\"'check'\" />\n }\n </div>\n <div class=\"fudis-multiselect-option__label__text\">\n <span class=\"fudis-multiselect-option__label__text__main\">{{ data.label }}</span>\n @if (data.subLabel) {\n <span class=\"fudis-multiselect-option__label__text__sub\">{{ data.subLabel }}</span>\n }\n\n @if (data.disabled) {\n <span class=\"fudis-multiselect-option__label__text__disabled\">\n ({{ _translationService.getTranslations()().SELECT.DISABLED }})</span\n >\n }\n </div>\n </label>\n</div>\n", styles: [".fudis-multiselect-option{color:#484848;width:auto}.fudis-multiselect-option:not(.fudis-multiselect-option--visible){display:none;visibility:hidden}.fudis-multiselect-option:hover:not(:has(.fudis-multiselect-option__label__checkbox__input[aria-disabled=true])){background-color:var(--fudis-color-primary-light)}.fudis-multiselect-option__disabled-text{padding-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-multiselect-option__label{text-transform:initial;line-height:calc(1.125rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:400;display:flex;align-items:flex-start;margin-bottom:0;padding:calc(.5rem / var(--fudis-rem-multiplier)) calc(.5rem / var(--fudis-rem-multiplier)) calc(.5rem / var(--fudis-rem-multiplier)) 0}.fudis-multiselect-option__label__checkbox{border:1px solid #727272;background-color:#fff;display:flex;position:absolute;align-items:center;justify-content:center;margin-left:calc(.5rem / var(--fudis-rem-multiplier));border-radius:0;width:calc(1rem / var(--fudis-rem-multiplier));height:calc(1rem / var(--fudis-rem-multiplier));cursor:pointer}.fudis-multiselect-option__label__checkbox--disabled{border:1px dashed #727272;background-color:#f1f1f1;cursor:default}.fudis-multiselect-option__label__checkbox__icon{transform:scale(.85)}.fudis-multiselect-option__label__checkbox__input{box-sizing:border-box;margin:0;padding:0;appearance:none;display:grid;position:absolute;place-content:center}.fudis-multiselect-option__label__checkbox__input:not([aria-disabled=true]):focus{outline:2px solid var(--fudis-color-primary);outline-offset:-2px;outline-offset:-1px;width:calc(1.25rem / var(--fudis-rem-multiplier));height:calc(1.25rem / var(--fudis-rem-multiplier))}.fudis-multiselect-option__label__text{position:relative;margin-left:calc(2rem / var(--fudis-rem-multiplier))}.fudis-multiselect-option__label__text__main{display:block}.fudis-multiselect-option__label__text__sub{text-transform:initial;line-height:calc(1rem / var(--fudis-rem-multiplier));font-family:Fira Sans,sans-serif;font-size:calc(.75rem / var(--fudis-rem-multiplier));font-weight:400;display:block;margin-top:2px;margin-bottom:2px}.fudis-multiselect-option__label__text__disabled{display:block}.fudis-multiselect-option--checked{color:#fff;background-color:var(--fudis-color-primary)}.fudis-multiselect-option--checked:hover{color:#484848}.fudis-multiselect-option--disabled{background-color:#f1f1f1;color:#727272;cursor:default}.fudis-multiselect-option--focused{background-color:var(--fudis-color-primary-light);color:#484848}\n"] }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: MultiselectComponent, decorators: [{
type: Host
}] }, { type: SelectGroupComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: FudisIdService }, { type: FudisTranslationService }] });
/**
* Enum representing pagination ellipsis markers\
* Used to indicate hidden page ranges.
*/
var Ellipsis;
(function (Ellipsis) {
Ellipsis["start"] = "start-ellipsis";
Ellipsis["end"] = "end-ellipsis";
})(Ellipsis || (Ellipsis = {}));
/**
* Controls navigation through paged data sets.
*
* Use this component to split large collections into manageable pages.
*/
class PaginationComponent {
constructor(_translationService, _idService, _elementRef) {
this._translationService = _translationService;
this._idService = _idService;
this._elementRef = _elementRef;
/**
* A function that increments **0-based page index** and returns the URL for that page. Example:
* `href=#2`
*/
this.pageHref = (i) => `#${i + 1}`;
/**
* On default behavior, focus will stay on pagination component. When set to false, application
* takes responsibility on focus handling on pageChange.
*/
this.autoFocusOnPageChange = true;
/**
* PageChange Emitter
*/
this.pageChange = new EventEmitter();
/**
* Page index has been changed
*/
this.hasUserChangedPage = false;
/**
* Protected signal for the total amount of pages
*/
this._pageCount = signal(0, ...(ngDevMode ? [{ debugName: "_pageCount" }] : /* istanbul ignore next */ []));
/**
* Protected signal for the current page index
*/
this._pageIndex = signal(0, ...(ngDevMode ? [{ debugName: "_pageIndex" }] : /* istanbul ignore next */ []));
/**
* Prefix for aria-label from Fudis translation keys
*/
this._paginationPrefix = new BehaviorSubject('');
/**
* Current page aria-label from Fudis translation keys
*/
this._paginationCurrent = new BehaviorSubject('');
/**
* Last page aria-label from Fudis translation keys
*/
this._paginationLast = new BehaviorSubject('');
/**
* Previous button label from Fudis translation keys
*/
this._paginationPreviousButton = new BehaviorSubject('');
/**
* Next button label from Fudis translation keys
*/
this._paginationNextButton = new BehaviorSubject('');
/**
* Previous button aria-label from Fudis translation keys
*/
this._paginationPreviousButtonAria = new BehaviorSubject('');
/**
* Next button aria-label from Fudis translation keys
*/
this._paginationNextButtonAria = new BehaviorSubject('');
/**
* Main CSS classes for buttons
*/
this._mainCssPrevClass = new BehaviorSubject([
'fudis-pagination-button fudis-pagination-button__previous',
]);
this._mainCssNextClass = new BehaviorSubject([
'fudis-pagination-button fudis-pagination-button__next',
]);
/**
* Computed list of pagination items (page numbers + ellipses)\
* Recalculated whenever page count, current page, or sibling count changes.
*/
this.itemList = computed(() => this.createPaginationItemList(this._pageCount(), this._pageIndex(), this._siblingCount()), ...(ngDevMode ? [{ debugName: "itemList" }] : /* istanbul ignore next */ []));
/**
* Returns an array of numbers from given starting to ending number
*/
this.range = (start, end) => {
const length = end - start + 1;
return Array.from({ length }, (_, i) => start + i);
};
/**
* Private signal for counting Pagination item visible siblings
*/
this._siblingCount = signal(2, ...(ngDevMode ? [{ debugName: "_siblingCount" }] : /* istanbul ignore next */ []));
/**
* After DOM is updated, restore focus to the new active element
*/
this.userSelectedIndex = -1;
/**
* Signal to track if a pagination button was clicked, and which one, to manage focus in
* ngAfterViewChecked
*/
this._buttonClickState = signal({ clicked: false, type: null }, ...(ngDevMode ? [{ debugName: "_buttonClickState" }] : /* istanbul ignore next */ []));
this._paginationPrefix.next(this._translationService.getTranslations()().PAGINATION.PREFIX);
this._paginationCurrent.next(this._translationService.getTranslations()().PAGINATION.CURRENT_PAGE);
this._paginationLast.next(this._translationService.getTranslations()().PAGINATION.LAST_PAGE);
this._paginationPreviousButton.next(this._translationService.getTranslations()().PAGINATION.BUTTON.PREVIOUS.LABEL);
this._paginationNextButton.next(this._translationService.getTranslations()().PAGINATION.BUTTON.NEXT.LABEL);
this._paginationPreviousButtonAria.next(this._translationService.getTranslations()().PAGINATION.BUTTON.PREVIOUS.ARIA_LABEL);
this._paginationNextButtonAria.next(this._translationService.getTranslations()().PAGINATION.BUTTON.NEXT.ARIA_LABEL);
/**
* Effect that tracks page index changes. Marks `hasUserChangedPage` as true when the current
* page differs from the previous one (ignoring the initial load).
*/
effect(() => {
const current = this._pageIndex();
if (this.prevPageIndex !== undefined &&
this.prevPageIndex !== current &&
!this.hasUserChangedPage) {
this.hasUserChangedPage = true;
}
this.prevPageIndex = current;
});
}
/**
* Set total amount of pages
*/
set pageCount(value) {
this._pageCount.set(value);
}
/**
* Set current page index
*/
set pageIndex(value) {
/**
* Prevent values that are outside of pageCount scope
*/
const safeValue = this._pageCount() && value >= 0 && value < this._pageCount()
? value
: Math.min(Math.max(value, 0), this._pageCount() - 1);
this._pageIndex.set(safeValue);
}
/**
* Internal input for setting the number of pages shown in each side of the current page
*/
set siblingCount(value) {
this._siblingCount.set(value);
}
/**
* Get translation for aria-live page openend announcement
*/
get liveAnnouncement() {
if (this._pageCount() === 0)
return '';
return (this._translationService.getTranslations()().PAGINATION.OPENED_PAGE +
` ${this._pageIndex() + 1}`);
}
/**
* Set button collapsed styles if Pagination container is smaller than < 465px
*/
_setClasses(collapsed) {
if (collapsed) {
this._mainCssPrevClass.next([
'fudis-pagination-button fudis-pagination-button__previous--collapsed',
]);
this._mainCssNextClass.next([
'fudis-pagination-button fudis-pagination-button__next--collapsed',
]);
}
else {
this._mainCssPrevClass.next(['fudis-pagination-button fudis-pagination-button__previous']);
this._mainCssNextClass.next(['fudis-pagination-button fudis-pagination-button__next']);
}
}
/**
* Create function that determines when to show ellipsis on start or end of the pagination list
*/
createPaginationItemList(pageCount, pageIndex, siblingCount) {
const startPages = this.range(1, Math.min(1, pageCount));
const endPages = this.range(Math.max(pageCount, 2), pageCount);
/**
* Calculate the first sibling page number to display around the current page
*/
const siblingsStart = Math.max(Math.min(pageIndex + 1 - siblingCount, pageCount - 1 - siblingCount * 2 - 1), 3);
/**
* Calculate the last sibling page number to display around the current page
*/
const siblingsEnd = Math.min(Math.max(pageIndex + 1 + siblingCount, 1 + siblingCount * 2 + 2), endPages.length > 0 ? endPages[0] - 2 : pageCount - 1);
return [
...startPages,
...(siblingsStart > 3 ? [Ellipsis.start] : pageCount - 1 > 2 ? [2] : []),
...this.range(siblingsStart, siblingsEnd),
...(siblingsEnd < pageCount - 2 ? [Ellipsis.end] : pageCount - 1 > 1 ? [pageCount - 1] : []),
...endPages,
];
}
ngOnInit() {
if (this.id) {
this._idService.addNewId('pagination', this.id);
}
else {
this.id = this._idService.getNewId('pagination');
}
this.observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const width = entry.contentRect.width;
// update sibling count based on width
if (width < 465) {
this._siblingCount.set(1);
this._setClasses(true);
}
else {
this._siblingCount.set(2);
this._setClasses(false);
}
}
});
this.observer.observe(this._elementRef.nativeElement);
}
/**
* After view updates, restore focus to the active page item if user selected one
*/
ngAfterViewChecked() {
if (this.userSelectedIndex >= 0 && this.activeItemRef) {
this.activeItemRef.nativeElement.focus();
this.userSelectedIndex = -1;
}
// Keep focus on clicked button
if (this._buttonClickState().clicked && this.autoFocusOnPageChange) {
const buttonType = this._buttonClickState().type;
this._buttonClickState.set({ clicked: false, type: null });
if (buttonType) {
document.getElementById(`${this.id}-button-${buttonType}`)?.focus();
}
}
}
ngOnDestroy() {
this.observer?.disconnect();
}
/**
* Emit pageChange event on pagination item click
*/
goToPage(index, event, wasButtonClick, buttonType) {
event?.preventDefault();
if (index < 0 || index >= this._pageCount())
return;
const lastPage = this._pageCount() - 1;
/**
* When autoFocusOnPageChange is true (default), move focus to the new active page item on page
* change. If autoFocusOnPageChange is false, let the application handle focus.
*/
if (this.autoFocusOnPageChange) {
if (wasButtonClick) {
// Set signal to track that a button was clicked and which one, so that focus can be moved to the correct button in ngAfterViewChecked
this._buttonClickState.set({ clicked: true, type: buttonType || null });
if (index === 0) {
// Move focus to first page item
this.userSelectedIndex = 0;
}
else if (index === lastPage) {
// Move focus to last page item
this.userSelectedIndex = lastPage;
}
else {
// Do not move focus on button click to middle pages
this.userSelectedIndex = -1;
}
}
else {
this._buttonClickState.set({ clicked: false, type: null });
// Set focus on clicking page numbers
this.userSelectedIndex = index;
}
}
this.pageChange.emit(index);
this._pageIndex.set(index);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PaginationComponent, deps: [{ token: FudisTranslationService }, { token: FudisIdService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: PaginationComponent, isStandalone: true, selector: "fudis-pagination", inputs: { id: "id", paginationAriaLabel: "paginationAriaLabel", pageCount: "pageCount", pageIndex: "pageIndex", siblingCount: "siblingCount", autoFocusOnPageChange: "autoFocusOnPageChange" }, outputs: { pageChange: "pageChange" }, viewQueries: [{ propertyName: "activeItemRef", first: true, predicate: ["activeItem"], descendants: true }], ngImport: i0, template: "<nav\n [id]=\"id\"\n class=\"fudis-pagination\"\n [attr.aria-label]=\"(_paginationPrefix | async) + ': ' + paginationAriaLabel\"\n>\n <span aria-atomic=\"true\" aria-live=\"polite\" class=\"fudis-visually-hidden\">\n {{ hasUserChangedPage ? liveAnnouncement : \"\" }}\n </span>\n @if (_pageIndex() > 0) {\n <button\n [id]=\"id + '-button-prev'\"\n [attr.aria-label]=\"((_paginationPreviousButtonAria | async) || '') + ' ' + _pageIndex()\"\n class=\"fudis-button fudis-button__tertiary fudis-button__size__medium {{\n _mainCssPrevClass | async\n }}\"\n (click)=\"goToPage(_pageIndex() - 1, $event, true, 'prev')\"\n >\n <span\n class=\"fudis-icon fudis-icon__lg fudis-icon__color__primary fudis-icon__chevron fudis-icon__rotate__flip-180\"\n ></span>\n <span class=\"{{ _mainCssPrevClass | async }}__text-content\">{{\n _paginationPreviousButton | async\n }}</span></button\n >\n }\n <ul class=\"fudis-pagination-list\">\n <ng-container>\n @for (item of itemList(); track item) {\n @if (item === \"start-ellipsis\" || item === \"end-ellipsis\") {\n <span aria-hidden=\"true\" class=\"fudis-pagination-list-item-ellipsis\">...</span>\n }\n @if (item !== \"start-ellipsis\" && item !== \"end-ellipsis\") {\n <li>\n @if (_pageIndex() + 1 === item) {\n <span\n #activeItem\n class=\"fudis-pagination-list-item fudis-pagination-list-item--active\"\n aria-current=\"page\"\n [attr.aria-label]=\"item + ', ' + (_paginationCurrent | async)\"\n tabindex=\"-1\"\n >\n {{ item }}\n </span>\n } @else {\n <a\n class=\"fudis-pagination-list-item fudis-pagination-list-item-link\"\n [href]=\"pageHref(item - 1)\"\n [attr.aria-label]=\"\n item === _pageCount() ? item + ', ' + (_paginationLast | async) : item\n \"\n (click)=\"goToPage(item - 1, $event)\"\n >\n {{ item }}\n </a>\n }\n </li>\n }\n }\n </ng-container>\n </ul>\n @if (_pageIndex() < _pageCount() - 1) {\n <button\n [id]=\"id + '-button-next'\"\n (click)=\"goToPage(_pageIndex() + 1, $event, true, 'next')\"\n [attr.aria-label]=\"((_paginationNextButtonAria | async) || '') + ' ' + (_pageIndex() + 2)\"\n class=\"fudis-button fudis-button__tertiary fudis-button__size__medium {{\n _mainCssNextClass | async\n }}\"\n >\n <span class=\"{{ _mainCssNextClass | async }}__text-content\">{{\n _paginationNextButton | async\n }}</span>\n <span class=\"fudis-icon fudis-icon__lg fudis-icon__color__primary fudis-icon__chevron\"></span>\n </button>\n }\n</nav>\n", styles: ["fudis-pagination{display:flex;justify-content:center}.fudis-pagination{display:flex}.fudis-pagination-button{padding-top:0!important;padding-bottom:0!important}.fudis-pagination-button__previous{margin-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-pagination-button__previous__text-content{display:block}.fudis-pagination-button__previous--collapsed{min-width:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-pagination-button__previous--collapsed__text-content{display:none}.fudis-pagination-button__next{margin-left:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-pagination-button__next__text-content{display:block}.fudis-pagination-button__next--collapsed{min-width:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-pagination-button__next--collapsed__text-content{display:none}.fudis-pagination-list{display:flex;align-items:baseline;margin:0;padding-left:0;list-style:none}.fudis-pagination-list-item{display:inline-block;margin-right:calc(.25rem / var(--fudis-rem-multiplier));border-radius:50%;width:calc(2rem / var(--fudis-rem-multiplier));height:calc(2rem / var(--fudis-rem-multiplier));font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:500}.fudis-pagination-list-item-ellipsis{display:block;margin-right:calc(.25rem / var(--fudis-rem-multiplier));width:calc(2rem / var(--fudis-rem-multiplier));height:calc(2rem / var(--fudis-rem-multiplier));text-align:center}.fudis-pagination-list-item-link{color:var(--fudis-color-primary);display:flex;position:relative;align-items:center;justify-content:center;text-decoration:underline solid;overflow-wrap:anywhere;font-family:Fira Sans,sans-serif}.fudis-pagination-list-item-link:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-pagination-list-item--active{color:var(--fudis-color-primary);border:2px solid var(--fudis-color-primary);display:flex;position:relative;align-items:center;justify-content:center;text-decoration:none;overflow-wrap:anywhere;font-family:Fira Sans,sans-serif}.fudis-pagination-list-item--active:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PaginationComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-pagination', imports: [CommonModule], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav\n [id]=\"id\"\n class=\"fudis-pagination\"\n [attr.aria-label]=\"(_paginationPrefix | async) + ': ' + paginationAriaLabel\"\n>\n <span aria-atomic=\"true\" aria-live=\"polite\" class=\"fudis-visually-hidden\">\n {{ hasUserChangedPage ? liveAnnouncement : \"\" }}\n </span>\n @if (_pageIndex() > 0) {\n <button\n [id]=\"id + '-button-prev'\"\n [attr.aria-label]=\"((_paginationPreviousButtonAria | async) || '') + ' ' + _pageIndex()\"\n class=\"fudis-button fudis-button__tertiary fudis-button__size__medium {{\n _mainCssPrevClass | async\n }}\"\n (click)=\"goToPage(_pageIndex() - 1, $event, true, 'prev')\"\n >\n <span\n class=\"fudis-icon fudis-icon__lg fudis-icon__color__primary fudis-icon__chevron fudis-icon__rotate__flip-180\"\n ></span>\n <span class=\"{{ _mainCssPrevClass | async }}__text-content\">{{\n _paginationPreviousButton | async\n }}</span></button\n >\n }\n <ul class=\"fudis-pagination-list\">\n <ng-container>\n @for (item of itemList(); track item) {\n @if (item === \"start-ellipsis\" || item === \"end-ellipsis\") {\n <span aria-hidden=\"true\" class=\"fudis-pagination-list-item-ellipsis\">...</span>\n }\n @if (item !== \"start-ellipsis\" && item !== \"end-ellipsis\") {\n <li>\n @if (_pageIndex() + 1 === item) {\n <span\n #activeItem\n class=\"fudis-pagination-list-item fudis-pagination-list-item--active\"\n aria-current=\"page\"\n [attr.aria-label]=\"item + ', ' + (_paginationCurrent | async)\"\n tabindex=\"-1\"\n >\n {{ item }}\n </span>\n } @else {\n <a\n class=\"fudis-pagination-list-item fudis-pagination-list-item-link\"\n [href]=\"pageHref(item - 1)\"\n [attr.aria-label]=\"\n item === _pageCount() ? item + ', ' + (_paginationLast | async) : item\n \"\n (click)=\"goToPage(item - 1, $event)\"\n >\n {{ item }}\n </a>\n }\n </li>\n }\n }\n </ng-container>\n </ul>\n @if (_pageIndex() < _pageCount() - 1) {\n <button\n [id]=\"id + '-button-next'\"\n (click)=\"goToPage(_pageIndex() + 1, $event, true, 'next')\"\n [attr.aria-label]=\"((_paginationNextButtonAria | async) || '') + ' ' + (_pageIndex() + 2)\"\n class=\"fudis-button fudis-button__tertiary fudis-button__size__medium {{\n _mainCssNextClass | async\n }}\"\n >\n <span class=\"{{ _mainCssNextClass | async }}__text-content\">{{\n _paginationNextButton | async\n }}</span>\n <span class=\"fudis-icon fudis-icon__lg fudis-icon__color__primary fudis-icon__chevron\"></span>\n </button>\n }\n</nav>\n", styles: ["fudis-pagination{display:flex;justify-content:center}.fudis-pagination{display:flex}.fudis-pagination-button{padding-top:0!important;padding-bottom:0!important}.fudis-pagination-button__previous{margin-right:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-pagination-button__previous__text-content{display:block}.fudis-pagination-button__previous--collapsed{min-width:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-pagination-button__previous--collapsed__text-content{display:none}.fudis-pagination-button__next{margin-left:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-pagination-button__next__text-content{display:block}.fudis-pagination-button__next--collapsed{min-width:calc(2.5rem / var(--fudis-rem-multiplier))}.fudis-pagination-button__next--collapsed__text-content{display:none}.fudis-pagination-list{display:flex;align-items:baseline;margin:0;padding-left:0;list-style:none}.fudis-pagination-list-item{display:inline-block;margin-right:calc(.25rem / var(--fudis-rem-multiplier));border-radius:50%;width:calc(2rem / var(--fudis-rem-multiplier));height:calc(2rem / var(--fudis-rem-multiplier));font-size:calc(.875rem / var(--fudis-rem-multiplier));font-weight:500}.fudis-pagination-list-item-ellipsis{display:block;margin-right:calc(.25rem / var(--fudis-rem-multiplier));width:calc(2rem / var(--fudis-rem-multiplier));height:calc(2rem / var(--fudis-rem-multiplier));text-align:center}.fudis-pagination-list-item-link{color:var(--fudis-color-primary);display:flex;position:relative;align-items:center;justify-content:center;text-decoration:underline solid;overflow-wrap:anywhere;font-family:Fira Sans,sans-serif}.fudis-pagination-list-item-link:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-pagination-list-item--active{color:var(--fudis-color-primary);border:2px solid var(--fudis-color-primary);display:flex;position:relative;align-items:center;justify-content:center;text-decoration:none;overflow-wrap:anywhere;font-family:Fira Sans,sans-serif}.fudis-pagination-list-item--active:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}\n"] }]
}], ctorParameters: () => [{ type: FudisTranslationService }, { type: FudisIdService }, { type: i0.ElementRef }], propDecorators: { activeItemRef: [{
type: ViewChild,
args: ['activeItem']
}], id: [{
type: Input
}], paginationAriaLabel: [{
type: Input,
args: [{ required: true }]
}], pageCount: [{
type: Input,
args: [{ required: true }]
}], pageIndex: [{
type: Input
}], siblingCount: [{
type: Input
}], autoFocusOnPageChange: [{
type: Input
}], pageChange: [{
type: Output
}] } });
/**
* Groups mutually exclusive options.
*
* Use this component when user must select exactly one option from a set. Each `fudis-radio-button`
* child must have an `[option]` input of type `FudisRadioButtonOption`.
*
* @example
* ```html
* <fudis-radio-button-group [label]="'Color'" [control]="colorControl">
* <fudis-radio-button [label]="'Red'" [value]="'red'"></fudis-radio-button>
* <fudis-radio-button [label]="'Blue'" [value]="'blue'"></fudis-radio-button>
* </fudis-radio-button-group>
* ```;
*/
class RadioButtonGroupComponent extends ControlComponentBaseDirective {
constructor(_document, _focusService, _idService) {
super(_idService, _focusService);
this._document = _document;
/**
* Width of Radio Button Group: 'sm' | 'md' | 'lg' | 'full-width'
*/
this.size = 'lg';
/**
* Emit form control and changed option when one option is clicked
*/
this.handleChange = new EventEmitter();
/**
* Private signals for managing the state and selected value of the radio button group
*/
this._selectedValue = signal(null, ...(ngDevMode ? [{ debugName: "_selectedValue" }] : /* istanbul ignore next */ []));
this._touched = signal(false, ...(ngDevMode ? [{ debugName: "_touched" }] : /* istanbul ignore next */ []));
this._invalid = signal(false, ...(ngDevMode ? [{ debugName: "_invalid" }] : /* istanbul ignore next */ []));
this._disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
/**
* Publicly exposed readonly signals for template binding and external use (in single radio
* button). Not meant to be set from outside the component, but reflect the current state of the
* form control and selected value.
*/
this.selectedValue = this._selectedValue.asReadonly();
this.touchedState = this._touched.asReadonly();
this.invalidState = this._invalid.asReadonly();
this.disabledState = this._disabled.asReadonly();
this._updateValueAndValidityTrigger.pipe(takeUntilDestroyed()).subscribe(() => {
if (this.control) {
this._required.next(FudisValidatorUtilities.required(this.control));
}
});
}
ngOnInit() {
this._setParentComponentId('radio-button-group');
this._updateValueAndValidityTrigger.next();
}
/**
* Sync local signals with FormControl states and add value and validity check when control value
* changes.
*/
ngOnChanges(changes) {
if (changes.control?.currentValue !== changes.control?.previousValue) {
this._syncControlState();
this._subscription?.unsubscribe();
this._subscription = this.control.events
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => {
this._syncControlState();
this._updateValueAndValidityTrigger.next();
});
}
}
ngAfterViewInit() {
super.ngAfterViewInit();
/**
* For Screen reader users bind guidance text to first radio child input
*/
const firstRadio = this._getFirstEnabledRadio();
const guidanceId = this._guidance?.['_id'];
if (firstRadio && guidanceId) {
firstRadio.setAttribute('aria-describedby', guidanceId);
}
}
/**
* Return first radio child input when it is not disabled
*/
_getFirstEnabledRadio() {
const radioIds = this._idService.getAllChildrenIds('radio-button-group', this.id);
if (!radioIds?.length)
return null;
const radio = this._document.getElementById(radioIds[0]);
if (!(radio instanceof HTMLInputElement) || radio.disabled) {
return null;
}
return radio;
}
/**
* Copy FormControl states into local signals
*/
_syncControlState() {
if (!this.control)
return;
this._selectedValue.set(this.control.value);
this._touched.set(this.control.touched);
this._invalid.set(this.control.invalid);
this._disabled.set(this.control.disabled);
}
triggerEmit(id, label) {
const data = {
option: {
id: id,
label: label,
value: this._selectedValue(),
},
control: this.control,
};
this.handleChange.emit(data);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: RadioButtonGroupComponent, deps: [{ token: DOCUMENT }, { token: FudisFocusService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: RadioButtonGroupComponent, isStandalone: true, selector: "fudis-radio-button-group", inputs: { size: "size" }, outputs: { handleChange: "handleChange" }, viewQueries: [{ propertyName: "_guidance", first: true, predicate: ["radioButtonGroupGuidance"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<fudis-fieldset\n class=\"fudis-radio-button-group\"\n [id]=\"id\"\n [labelSize]=\"'sm'\"\n [label]=\"label\"\n [required]=\"_required | async\"\n [errorSummaryBreadcrumb]=\"false\"\n [inputSize]=\"size\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n [initialFocus]=\"initialFocus\"\n>\n <fudis-fieldset-content>\n <ng-content select=\"fudis-radio-button\" />\n @if (control && !disableGuidance) {\n <fudis-guidance\n #radioButtonGroupGuidance\n [inputLabel]=\"label\"\n [helpText]=\"helpText\"\n [for]=\"id + '-legend'\"\n [control]=\"control\"\n [groupHelpTextHidden]=\"true\"\n >\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n </fudis-fieldset-content>\n</fudis-fieldset>\n", dependencies: [{ kind: "component", type: FieldSetComponent, selector: "fudis-fieldset", inputs: ["label", "width", "inputSize", "align", "initialFocus", "errorSummaryBreadcrumb", "required", "labelSize", "id", "helpText"] }, { kind: "directive", type: FieldsetContentDirective, selector: "fudis-fieldset-content" }, { kind: "component", type: GuidanceComponent, selector: "fudis-guidance", inputs: ["for", "inputLabel", "control", "formGroup", "helpText", "maxLength", "selectedOption", "groupBlurredOut", "groupHelpTextHidden"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: RadioButtonGroupComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-radio-button-group', changeDetection: ChangeDetectionStrategy.OnPush, imports: [FieldSetComponent, FieldsetContentDirective, GuidanceComponent, AsyncPipe], template: "<fudis-fieldset\n class=\"fudis-radio-button-group\"\n [id]=\"id\"\n [labelSize]=\"'sm'\"\n [label]=\"label\"\n [required]=\"_required | async\"\n [errorSummaryBreadcrumb]=\"false\"\n [inputSize]=\"size\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n [initialFocus]=\"initialFocus\"\n>\n <fudis-fieldset-content>\n <ng-content select=\"fudis-radio-button\" />\n @if (control && !disableGuidance) {\n <fudis-guidance\n #radioButtonGroupGuidance\n [inputLabel]=\"label\"\n [helpText]=\"helpText\"\n [for]=\"id + '-legend'\"\n [control]=\"control\"\n [groupHelpTextHidden]=\"true\"\n >\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n </fudis-fieldset-content>\n</fudis-fieldset>\n" }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: FudisFocusService }, { type: FudisIdService }], propDecorators: { _guidance: [{
type: ViewChild,
args: ['radioButtonGroupGuidance']
}], size: [{
type: Input
}], handleChange: [{
type: Output
}] } });
/**
* Single radio button option for RadioButtonGroupComponent.
*/
class RadioButtonComponent {
constructor(_idService, _parentGroup) {
this._idService = _idService;
this._parentGroup = _parentGroup;
/**
* Emits changed Radio button option and form control.
*/
this.handleChange = new EventEmitter();
}
/**
* Selected Radio Button change
*/
_onChange() {
const optionToEmit = {
id: this._id,
label: this.label,
value: this._parentGroup?.selectedValue(),
};
/**
* This Radio button's emit
*/
this.handleChange.emit({ option: optionToEmit, control: this._parentGroup.control });
/**
* Call parent's function, which triggers Radio Button Group's emit
*/
this._parentGroup.triggerEmit(this._id, this.label);
}
ngOnInit() {
if (this._id) {
this._idService.addNewChildId('radio-button-group', this._parentGroup.id, this._id);
}
else {
this._id = this._idService.getNewChildId('radio-button-group', this._parentGroup.id);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: RadioButtonComponent, deps: [{ token: FudisIdService }, { token: RadioButtonGroupComponent, host: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: RadioButtonComponent, isStandalone: true, selector: "fudis-radio-button", inputs: { value: "value", label: "label" }, outputs: { handleChange: "handleChange" }, ngImport: i0, template: "@if (_parentGroup?.control) {\n <label\n class=\"fudis-radio-button\"\n [attr.for]=\"_id\"\n [class.fudis-radio-button--disabled]=\"_parentGroup.disabled\"\n >\n <input\n type=\"radio\"\n class=\"fudis-radio-button__input\"\n [class.fudis-radio-button__input--disabled]=\"\n _parentGroup.disabledState() || _parentGroup.disabled || null\n \"\n [id]=\"_id\"\n [formControl]=\"_parentGroup.control\"\n [value]=\"value\"\n [name]=\"_parentGroup.id\"\n [attr.name]=\"_parentGroup.id\"\n [attr.tabindex]=\"_parentGroup.disabledState() || _parentGroup.disabled ? -1 : null\"\n [attr.aria-disabled]=\"_parentGroup.disabledState() || _parentGroup.disabled || null\"\n [attr.aria-invalid]=\"(_parentGroup.touchedState() && _parentGroup.invalidState()) || null\"\n (focus)=\"_parentGroup.onFocus($event)\"\n (keyup)=\"_parentGroup.handleKeyUp.emit($event)\"\n (blur)=\"_parentGroup.handleBlur.emit($event)\"\n (change)=\"_onChange()\"\n />\n <div\n class=\"fudis-radio-button__content\"\n [class.fudis-radio-button__content--disabled]=\"\n _parentGroup.disabledState() || _parentGroup.disabled\n \"\n >\n <div class=\"fudis-radio-button__content-wrapper\">\n <div\n class=\"fudis-radio-button__content__control\"\n [class.fudis-radio-button__content__control--invalid]=\"\n _parentGroup.touchedState() && _parentGroup.invalidState()\n \"\n [class.fudis-radio-button__content__control--disabled]=\"\n _parentGroup.disabledState() || _parentGroup.disabled\n \"\n >\n @if (_parentGroup.selectedValue() === value) {\n <span class=\"fudis-radio-button__content__control__indicator\"></span>\n }\n </div>\n </div>\n <span class=\"fudis-radio-button__label\">{{ label }}</span>\n </div>\n </label>\n}\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: RadioButtonComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-radio-button', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormsModule, ReactiveFormsModule], template: "@if (_parentGroup?.control) {\n <label\n class=\"fudis-radio-button\"\n [attr.for]=\"_id\"\n [class.fudis-radio-button--disabled]=\"_parentGroup.disabled\"\n >\n <input\n type=\"radio\"\n class=\"fudis-radio-button__input\"\n [class.fudis-radio-button__input--disabled]=\"\n _parentGroup.disabledState() || _parentGroup.disabled || null\n \"\n [id]=\"_id\"\n [formControl]=\"_parentGroup.control\"\n [value]=\"value\"\n [name]=\"_parentGroup.id\"\n [attr.name]=\"_parentGroup.id\"\n [attr.tabindex]=\"_parentGroup.disabledState() || _parentGroup.disabled ? -1 : null\"\n [attr.aria-disabled]=\"_parentGroup.disabledState() || _parentGroup.disabled || null\"\n [attr.aria-invalid]=\"(_parentGroup.touchedState() && _parentGroup.invalidState()) || null\"\n (focus)=\"_parentGroup.onFocus($event)\"\n (keyup)=\"_parentGroup.handleKeyUp.emit($event)\"\n (blur)=\"_parentGroup.handleBlur.emit($event)\"\n (change)=\"_onChange()\"\n />\n <div\n class=\"fudis-radio-button__content\"\n [class.fudis-radio-button__content--disabled]=\"\n _parentGroup.disabledState() || _parentGroup.disabled\n \"\n >\n <div class=\"fudis-radio-button__content-wrapper\">\n <div\n class=\"fudis-radio-button__content__control\"\n [class.fudis-radio-button__content__control--invalid]=\"\n _parentGroup.touchedState() && _parentGroup.invalidState()\n \"\n [class.fudis-radio-button__content__control--disabled]=\"\n _parentGroup.disabledState() || _parentGroup.disabled\n \"\n >\n @if (_parentGroup.selectedValue() === value) {\n <span class=\"fudis-radio-button__content__control__indicator\"></span>\n }\n </div>\n </div>\n <span class=\"fudis-radio-button__label\">{{ label }}</span>\n </div>\n </label>\n}\n" }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: RadioButtonGroupComponent, decorators: [{
type: Host
}] }], propDecorators: { value: [{
type: Input,
args: [{ required: true }]
}], label: [{
type: Input,
args: [{ required: true }]
}], handleChange: [{
type: Output
}] } });
/**
* Defines a logical content section.
*
* Use this component to group related content and improve page structure.
*/
class SectionComponent extends PopoverApiDirective {
constructor(_element, _idService, _errorSummaryService) {
super();
this._element = _element;
this._idService = _idService;
this._errorSummaryService = _errorSummaryService;
/**
* Maximum width of Grid. When viewport gets narrower, grid automatically adjusts to lower sizes.
*
* - Xxl = Default value. Viewports of 1600px and larger
* - Xl = Viewports smaller than 1600px
* - Lg = Viewports smaller than 1200px
* - Md = Viewports smaller than 992px
* - Sm = Viewports smaller than 768px
* - Xs = Viewports smaller than 576px
* - Full-width = Grid takes full width of its parent container, no maximum width
*/
this.width = 'initial';
/**
* Alignment of Grid component inside its parent
*/
this.align = 'start';
/**
* Is section title shown in error summary breadcrumb
*/
this.errorSummaryBreadcrumb = false;
/**
* Section CSS class list
*/
this._classList = new BehaviorSubject('');
/**
* Is info sent to error summary service
*/
this._errorSummaryInfoSent = false;
}
ngOnInit() {
this._setSectionId();
if (!this.titleVariant) {
this.titleVariant = getHeadingVariant(this.level);
}
this._headingId = `${this.id}-heading`;
this._classList.next(this._getClasses());
this._addToErrorSummary();
}
ngAfterContentInit() {
this._errorSummaryService
.getFormAncestorId(this._element.nativeElement)
.then((parentFormId) => {
if (parentFormId) {
this._parentFormId = parentFormId;
this._addToErrorSummary();
}
});
}
ngOnChanges(changes) {
if (changes.classes?.currentValue !== changes.classes?.previousValue) {
this._classList.next(this._getClasses());
}
if (changes.title?.currentValue !== changes.title?.previousValue && this.id) {
this._addToErrorSummary();
}
}
ngOnDestroy() {
this._removeFromErrorSummary();
}
/**
* Send error object to Error Summary Service
*/
_addToErrorSummary() {
if (this.errorSummaryBreadcrumb && this._parentFormId) {
const errorSummaryInfo = {
id: this.id,
formId: this._parentFormId,
title: this.title,
};
this._errorSummaryService.addSection(errorSummaryInfo);
this._errorSummaryInfoSent = true;
}
}
/**
* Remove error object from Error Summary Service
*/
_removeFromErrorSummary() {
if (this._errorSummaryInfoSent && this._parentFormId) {
this._errorSummaryService.removeSection(this._parentFormId, this.id);
}
}
/**
* Set main CSS class with possible custom classes
*/
_getClasses() {
if (this.classes) {
return `fudis-section ${this.classes}`;
}
else {
return 'fudis-section';
}
}
/**
* Generate id with Id Service
*/
_setSectionId() {
if (this.id) {
this._idService.addNewId('section', this.id);
}
else {
this.id = this._idService.getNewId('section');
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SectionComponent, deps: [{ token: i0.ElementRef }, { token: FudisIdService }, { token: FudisInternalErrorSummaryService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SectionComponent, isStandalone: true, selector: "fudis-section", inputs: { title: "title", level: "level", titleVariant: "titleVariant", id: "id", badge: "badge", badgeText: "badgeText", width: "width", align: "align", classes: "classes", errorSummaryBreadcrumb: "errorSummaryBreadcrumb" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<!-- prettier-ignore -->\n@if (_classList | async; as classes) {\n <section\n fudisGrid\n [id]=\"id\"\n [tabIndex]=\"-1\"\n [attr.aria-labelledby]=\"_headingId\"\n [align]=\"align\"\n [width]=\"width\"\n [classes]=\"classes\"\n [rowGap]=\"'none'\"\n [serviceDefaults]=\"false\"\n >\n <div class=\"fudis-section__header\" [id]=\"_headingId\">\n <div class=\"fudis-section__header__container\">\n <div class=\"fudis-section__header__heading\">\n <fudis-heading class=\"fudis-mb-none\" [level]=\"level\" [variant]=\"titleVariant\"\n >{{ title }}@if (badge && badgeText) {\n <fudis-badge\n class=\"fudis-section__header__heading__badge\"\n [variant]=\"badge\"\n [content]=\"badgeText\"\n />\n }\n </fudis-heading>\n @if (popoverTriggerLabel && popoverText) {\n <fudis-icon-button\n class=\"fudis-section__header__heading__popover\"\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'info-circle'\"\n [ariaLabel]=\"popoverTriggerLabel\"\n [popoverPosition]=\"popoverPosition\"\n [popoverText]=\"popoverText\"\n />\n }\n </div>\n <ng-content select=\"fudis-section-actions\"></ng-content>\n </div>\n </div>\n <ng-content select=\"fudis-section-content\"></ng-content>\n </section>\n}\n", styles: [".fudis-section{box-sizing:border-box;padding:0}.fudis-section__header__heading{display:flex;align-items:center}.fudis-section__header__heading__badge,.fudis-section__header__heading__popover{margin-left:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-section__header__container{display:flex;flex-wrap:wrap;align-items:center;margin-bottom:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-section-actions{display:flex;flex-wrap:wrap;gap:calc(.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));margin-left:auto}.fudis-section-content fudis-notification{margin-bottom:calc(.5rem / var(--fudis-rem-multiplier))}\n"], dependencies: [{ kind: "directive", type: GridDirective, selector: "[fudisGrid]" }, { kind: "component", type: HeadingComponent, selector: "fudis-heading", inputs: ["level", "variant", "id", "align"] }, { kind: "component", type: BadgeComponent, selector: "fudis-badge", inputs: ["variant", "content"] }, { kind: "component", type: IconButtonComponent, selector: "fudis-icon-button", inputs: ["ariaLabel", "icon", "asMenuButton"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SectionComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-section', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [GridDirective, HeadingComponent, BadgeComponent, IconButtonComponent, AsyncPipe], template: "<!-- prettier-ignore -->\n@if (_classList | async; as classes) {\n <section\n fudisGrid\n [id]=\"id\"\n [tabIndex]=\"-1\"\n [attr.aria-labelledby]=\"_headingId\"\n [align]=\"align\"\n [width]=\"width\"\n [classes]=\"classes\"\n [rowGap]=\"'none'\"\n [serviceDefaults]=\"false\"\n >\n <div class=\"fudis-section__header\" [id]=\"_headingId\">\n <div class=\"fudis-section__header__container\">\n <div class=\"fudis-section__header__heading\">\n <fudis-heading class=\"fudis-mb-none\" [level]=\"level\" [variant]=\"titleVariant\"\n >{{ title }}@if (badge && badgeText) {\n <fudis-badge\n class=\"fudis-section__header__heading__badge\"\n [variant]=\"badge\"\n [content]=\"badgeText\"\n />\n }\n </fudis-heading>\n @if (popoverTriggerLabel && popoverText) {\n <fudis-icon-button\n class=\"fudis-section__header__heading__popover\"\n [size]=\"'extra-small'\"\n [variant]=\"'tertiary'\"\n [icon]=\"'info-circle'\"\n [ariaLabel]=\"popoverTriggerLabel\"\n [popoverPosition]=\"popoverPosition\"\n [popoverText]=\"popoverText\"\n />\n }\n </div>\n <ng-content select=\"fudis-section-actions\"></ng-content>\n </div>\n </div>\n <ng-content select=\"fudis-section-content\"></ng-content>\n </section>\n}\n", styles: [".fudis-section{box-sizing:border-box;padding:0}.fudis-section__header__heading{display:flex;align-items:center}.fudis-section__header__heading__badge,.fudis-section__header__heading__popover{margin-left:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-section__header__container{display:flex;flex-wrap:wrap;align-items:center;margin-bottom:calc(.5rem / var(--fudis-rem-multiplier))}.fudis-section-actions{display:flex;flex-wrap:wrap;gap:calc(.5rem / var(--fudis-rem-multiplier)) calc(1rem / var(--fudis-rem-multiplier));margin-left:auto}.fudis-section-content fudis-notification{margin-bottom:calc(.5rem / var(--fudis-rem-multiplier))}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: FudisIdService }, { type: FudisInternalErrorSummaryService }], propDecorators: { title: [{
type: Input,
args: [{ required: true }]
}], level: [{
type: Input,
args: [{ required: true }]
}], titleVariant: [{
type: Input
}], id: [{
type: Input
}], badge: [{
type: Input
}], badgeText: [{
type: Input
}], width: [{
type: Input
}], align: [{
type: Input
}], classes: [{
type: Input
}], errorSummaryBreadcrumb: [{
type: Input
}] } });
/**
* Organizes content into selectable tabs.
*
* Use this component to switch between related views within the same context.
*/
class TabNavigationBarComponent {
constructor() {
/**
* Variant option
*/
this.variant = 'primary';
this._leftContentHidden = signal(false, ...(ngDevMode ? [{ debugName: "_leftContentHidden" }] : /* istanbul ignore next */ []));
this._rightContentHidden = signal(false, ...(ngDevMode ? [{ debugName: "_rightContentHidden" }] : /* istanbul ignore next */ []));
this._ariaControls = signal(undefined, ...(ngDevMode ? [{ debugName: "_ariaControls" }] : /* istanbul ignore next */ []));
this._scrollAmount = 150;
this.getAriaControls = () => this._ariaControls;
this.isScrollable = () => {
return this._leftContentHidden() || this._rightContentHidden();
};
this._scrollTo = (element, scrollAmount, behavior = 'smooth') => {
element.scrollTo({
left: scrollAmount,
behavior: behavior,
});
};
this._updatePanel = () => {
if (this._tabPanel) {
if (this._currentActiveTabId)
this._tabPanel.setActiveTabId(this._currentActiveTabId);
if (!this._ariaControls())
this._ariaControls.set(this._tabPanel.id);
}
};
}
/**
* Reference to the TabNavigationPanelComponent
*/
get panel() {
return this._tabPanel;
}
set panel(value) {
this._tabPanel = value;
this._updatePanel();
}
ngAfterViewInit() {
this._resizeObserver = new ResizeObserver(() => {
this.assertScroll();
});
if (this.tabNavigation)
this._resizeObserver.observe(this.tabNavigation.nativeElement);
if (this.scrollContainer) {
this._scrollSubscription = fromEvent(this.scrollContainer.nativeElement, 'scroll')
.pipe(auditTime(300))
.subscribe(() => this.assertScroll());
}
if (this.ariaControls && !this._ariaControls())
this._ariaControls.set(this.ariaControls);
}
assertScroll() {
const scrollContainer = this.scrollContainer?.nativeElement;
if (scrollContainer) {
const isLeftHidden = scrollContainer.scrollLeft > 0;
const isRightHidden = Math.round(scrollContainer.scrollLeft + scrollContainer.clientWidth) <
scrollContainer.scrollWidth;
this._rightContentHidden.set(isRightHidden);
this._leftContentHidden.set(isLeftHidden);
}
}
scrollLeft(scrollToStart) {
const scrollContainer = this.scrollContainer?.nativeElement;
if (scrollContainer) {
if (scrollToStart) {
this._scrollTo(scrollContainer, 0, 'instant');
}
else {
const targetScrollLeft = Math.max(0, scrollContainer.scrollLeft - this._scrollAmount);
this._scrollTo(scrollContainer, targetScrollLeft);
}
}
}
scrollRight(scrollToEnd) {
const scrollContainer = this.scrollContainer.nativeElement;
if (scrollContainer) {
const maxScrollLeft = scrollContainer.scrollWidth - scrollContainer.clientWidth;
if (scrollToEnd) {
this._scrollTo(scrollContainer, maxScrollLeft, 'instant');
}
else {
const targetScrollLeft = Math.min(maxScrollLeft, scrollContainer.scrollLeft + this._scrollAmount);
this._scrollTo(scrollContainer, targetScrollLeft);
}
}
}
updateActiveLink(id) {
this._currentActiveTabId = id;
this._updatePanel();
}
ngOnDestroy() {
this._resizeObserver?.disconnect();
this._scrollSubscription?.unsubscribe();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TabNavigationBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TabNavigationBarComponent, isStandalone: true, selector: "fudis-tab-navigation-bar", inputs: { id: "id", panel: "panel", ariaControls: "ariaControls", variant: "variant" }, host: { attributes: { "role": "tablist" }, properties: { "id": "id" } }, viewQueries: [{ propertyName: "tabNavigation", first: true, predicate: ["tabNavigation"], descendants: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true }], ngImport: i0, template: "<div #tabNavigation class=\"fudis-tab-navigation-bar\">\n @if (_leftContentHidden()) {\n <div\n class=\"fudis-tab-navigation-bar__button-container fudis-tab-navigation-bar__button-container--left\"\n >\n <button\n [id]=\"id + '-scroll-button-left'\"\n (click)=\"scrollLeft()\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [class]=\"'fudis-tab-navigation-bar__button-container__button fudis-tab-navigation-bar__button-container__button--left'\"\n >\n <fudis-icon\n class=\"fudis-tab-navigation-bar__button-container__button__icon\"\n rotate=\"flip-180\"\n icon=\"chevron-ring-fill\"\n ></fudis-icon>\n </button>\n </div>\n }\n <div [class]=\"'fudis-tab-navigation-bar__wrapper fudis-tab-navigation-bar__wrapper--' + variant\">\n <div\n #scrollContainer\n [class]=\"\n 'fudis-tab-navigation-bar__wrapper__tab-container fudis-tab-navigation-bar__wrapper__tab-container--' +\n variant\n \"\n >\n <ng-content></ng-content>\n </div>\n </div>\n @if (_rightContentHidden()) {\n <div\n class=\"fudis-tab-navigation-bar__button-container fudis-tab-navigation-bar__button-container--right\"\n >\n <button\n [id]=\"id + '-scroll-button-right'\"\n (click)=\"scrollRight()\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [class]=\"'fudis-tab-navigation-bar__button-container__button fudis-tab-navigation-bar__button-container__button--right'\"\n >\n <fudis-icon\n class=\"fudis-tab-navigation-bar__button-container__button__icon\"\n icon=\"chevron-ring-fill\"\n ></fudis-icon>\n </button>\n </div>\n }\n</div>\n", styles: [".fudis-tab-navigation-bar__button-container{background-color:#fff}.fudis-tab-navigation-bar__button-container--right{display:flex;position:absolute;top:0;right:0;align-items:flex-start;justify-content:flex-end;width:calc(2.5rem / var(--fudis-rem-multiplier));height:calc(3rem / var(--fudis-rem-multiplier))}.fudis-tab-navigation-bar__button-container--left{display:flex;position:absolute;top:0;left:0;align-items:flex-start;justify-content:flex-start;width:calc(2.5rem / var(--fudis-rem-multiplier));height:calc(3rem / var(--fudis-rem-multiplier))}.fudis-tab-navigation-bar__button-container__button{background-color:#fff;position:absolute;top:50%;transform:translateY(-50%);border-style:none;padding:0;cursor:pointer}.fudis-tab-navigation-bar__button-container__button:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-tab-navigation-bar__button-container__button--right{right:0}.fudis-tab-navigation-bar__button-container__button--left{left:0}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "fudis-icon", inputs: ["icon", "color", "rotate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TabNavigationBarComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-tab-navigation-bar', imports: [IconComponent], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
'[id]': 'id',
role: 'tablist',
}, template: "<div #tabNavigation class=\"fudis-tab-navigation-bar\">\n @if (_leftContentHidden()) {\n <div\n class=\"fudis-tab-navigation-bar__button-container fudis-tab-navigation-bar__button-container--left\"\n >\n <button\n [id]=\"id + '-scroll-button-left'\"\n (click)=\"scrollLeft()\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [class]=\"'fudis-tab-navigation-bar__button-container__button fudis-tab-navigation-bar__button-container__button--left'\"\n >\n <fudis-icon\n class=\"fudis-tab-navigation-bar__button-container__button__icon\"\n rotate=\"flip-180\"\n icon=\"chevron-ring-fill\"\n ></fudis-icon>\n </button>\n </div>\n }\n <div [class]=\"'fudis-tab-navigation-bar__wrapper fudis-tab-navigation-bar__wrapper--' + variant\">\n <div\n #scrollContainer\n [class]=\"\n 'fudis-tab-navigation-bar__wrapper__tab-container fudis-tab-navigation-bar__wrapper__tab-container--' +\n variant\n \"\n >\n <ng-content></ng-content>\n </div>\n </div>\n @if (_rightContentHidden()) {\n <div\n class=\"fudis-tab-navigation-bar__button-container fudis-tab-navigation-bar__button-container--right\"\n >\n <button\n [id]=\"id + '-scroll-button-right'\"\n (click)=\"scrollRight()\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [class]=\"'fudis-tab-navigation-bar__button-container__button fudis-tab-navigation-bar__button-container__button--right'\"\n >\n <fudis-icon\n class=\"fudis-tab-navigation-bar__button-container__button__icon\"\n icon=\"chevron-ring-fill\"\n ></fudis-icon>\n </button>\n </div>\n }\n</div>\n", styles: [".fudis-tab-navigation-bar__button-container{background-color:#fff}.fudis-tab-navigation-bar__button-container--right{display:flex;position:absolute;top:0;right:0;align-items:flex-start;justify-content:flex-end;width:calc(2.5rem / var(--fudis-rem-multiplier));height:calc(3rem / var(--fudis-rem-multiplier))}.fudis-tab-navigation-bar__button-container--left{display:flex;position:absolute;top:0;left:0;align-items:flex-start;justify-content:flex-start;width:calc(2.5rem / var(--fudis-rem-multiplier));height:calc(3rem / var(--fudis-rem-multiplier))}.fudis-tab-navigation-bar__button-container__button{background-color:#fff;position:absolute;top:50%;transform:translateY(-50%);border-style:none;padding:0;cursor:pointer}.fudis-tab-navigation-bar__button-container__button:focus{outline:2px dashed #484848;outline-offset:1px;box-shadow:0 0 0 calc(.25rem / var(--fudis-rem-multiplier)) #fff}.fudis-tab-navigation-bar__button-container__button--right{right:0}.fudis-tab-navigation-bar__button-container__button--left{left:0}\n"] }]
}], propDecorators: { id: [{
type: Input
}], panel: [{
type: Input
}], ariaControls: [{
type: Input
}], variant: [{
type: Input
}], tabNavigation: [{
type: ViewChild,
args: ['tabNavigation']
}], scrollContainer: [{
type: ViewChild,
args: ['scrollContainer']
}] } });
/**
* Navigation tab panel associated with the TabNavigationBarComponent.
*/
class TabNavigationPanelComponent {
constructor() {
this.setActiveTabId = (id) => (this._activeTabId = id);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TabNavigationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: TabNavigationPanelComponent, isStandalone: true, selector: "fudis-tab-navigation-panel", inputs: { id: "id" }, host: { attributes: { "role": "tabpanel" }, properties: { "attr.aria-labelledby": "_activeTabId", "id": "id" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TabNavigationPanelComponent, decorators: [{
type: Component,
args: [{
selector: 'fudis-tab-navigation-panel',
encapsulation: ViewEncapsulation.None,
template: '<ng-content></ng-content>',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[attr.aria-labelledby]': '_activeTabId',
'[id]': 'id',
role: 'tabpanel',
},
}]
}], propDecorators: { id: [{
type: Input
}] } });
/**
* Button or a link inside a `fudis-tab-navigation-bar`.
*/
class TabNavigationTabComponent {
/**
* Currently active tab
*/
get active() {
return this._isActive;
}
set active(value) {
if (value !== this._isActive) {
this._isActive = value;
if (this._isActive)
this._tabNavigation?.updateActiveLink(this.id);
}
}
constructor() {
this._tabNavigation = inject(TabNavigationBarComponent);
this._isActive = false;
this._onKeyDown = (event) => {
if (event.key === ' ' && event?.target instanceof HTMLAnchorElement) {
event.preventDefault();
const anchorElement = event.target;
anchorElement.click();
}
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight')
this._onArrowNavigation(event);
};
this._onArrowNavigation = (event) => {
event.preventDefault();
const tabElements = this._getTabs();
const currentElement = this._tabNavigation?.scrollContainer?.nativeElement?.querySelector(':focus');
if (currentElement && tabElements.length > 0) {
const currentIndex = tabElements.indexOf(currentElement);
let nextIndex = currentIndex;
let focusElement = currentElement;
if (event.key === 'ArrowLeft') {
event.preventDefault();
nextIndex = currentIndex > 0 ? currentIndex - 1 : tabElements.length - 1;
focusElement = tabElements[nextIndex];
}
else if (event.key === 'ArrowRight') {
event.preventDefault();
nextIndex = currentIndex < tabElements.length - 1 ? currentIndex + 1 : 0;
focusElement = tabElements[nextIndex];
}
focusElement?.focus();
if (this._tabNavigation.isScrollable()) {
if (nextIndex === 0) {
this._tabNavigation?.scrollLeft(true);
}
else if (nextIndex === tabElements.length - 1) {
this._tabNavigation?.scrollRight(true);
}
else {
focusElement.scrollIntoView({
behavior: 'smooth',
inline: 'center',
block: 'nearest',
});
}
}
}
};
this._getTabs = () => {
const tabNodes = this._tabNavigation.scrollContainer?.nativeElement?.querySelectorAll('[fudis-tab-navigation-tab]');
return [...tabNodes];
};
effect(() => {
this._ariaControls = this._tabNavigation.getAriaControls()();
});
}
ngAfterViewInit() {
if (this._isActive)
this._tabNavigation?.updateActiveLink(this.id);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TabNavigationTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: TabNavigationTabComponent, isStandalone: true, selector: "[fudis-tab-navigation-tab]", inputs: { id: "id", active: "active" }, host: { attributes: { "role": "tab" }, listeners: { "keydown": "_onKeyDown($event)" }, properties: { "id": "id", "attr.tabIndex": "_isActive ? 0 : -1", "attr.aria-selected": "_isActive", "attr.aria-controls": "_ariaControls", "class": "'fudis-tab-navigation-tab fudis-tab-navigation-tab--' + _tabNavigation.variant", "class.fudis-tab-navigation-tab--primary--active": "_tabNavigation.variant === 'primary' && _isActive", "class.fudis-tab-navigation-tab--secondary--active": "_tabNavigation.variant === 'secondary' && _isActive" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TabNavigationTabComponent, decorators: [{
type: Component,
args: [{
selector: '[fudis-tab-navigation-tab]',
encapsulation: ViewEncapsulation.None,
template: '<ng-content></ng-content>',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[id]': 'id',
role: 'tab',
'(keydown)': '_onKeyDown($event)',
'[attr.tabIndex]': '_isActive ? 0 : -1',
'[attr.aria-selected]': '_isActive',
'[attr.aria-controls]': '_ariaControls',
'[class]': "'fudis-tab-navigation-tab fudis-tab-navigation-tab--' + _tabNavigation.variant",
'[class.fudis-tab-navigation-tab--primary--active]': "_tabNavigation.variant === 'primary' && _isActive",
'[class.fudis-tab-navigation-tab--secondary--active]': "_tabNavigation.variant === 'secondary' && _isActive",
},
}]
}], ctorParameters: () => [], propDecorators: { id: [{
type: Input
}], active: [{
type: Input
}] } });
class TextFieldComponentBaseDirective extends ControlComponentBaseDirective {
constructor(_idService, _focusService) {
super(_idService, _focusService);
/**
* Width of the input field: 'sm' | 'md' | 'lg'
*/
this.size = 'lg';
/**
* If user clears the input field, set FormControl value to null instead of empty string.
*/
this.nullControlOnEmptyString = true;
/**
* Max length for HTML attribute and for character indicator in guidance.
*/
this._maxLength = new BehaviorSubject(null);
/**
* Min length for HTML attribute.
*/
this._minLength = new BehaviorSubject(null);
}
/**
* Depending on input prop 'nullControlOnEmptyString' either subscribe or unsubscribe to control's
* value changes
*/
_setControlValueSubscription() {
if (this.nullControlOnEmptyString) {
this._baseSubscription?.unsubscribe();
this._baseSubscription = this.control.valueChanges
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe((value) => {
if (typeof value === 'string' && value.trim() === '') {
this.control.setValue(null);
}
});
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextFieldComponentBaseDirective, deps: [{ token: FudisIdService }, { token: FudisFocusService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: TextFieldComponentBaseDirective, isStandalone: true, selector: "[fudisTextFieldComponentBase]", inputs: { size: "size", nullControlOnEmptyString: "nullControlOnEmptyString" }, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextFieldComponentBaseDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisTextFieldComponentBase]' }]
}], ctorParameters: () => [{ type: FudisIdService }, { type: FudisFocusService }], propDecorators: { size: [{
type: Input
}], nullControlOnEmptyString: [{
type: Input
}] } });
/**
* Allows entry of multi-line text.
*
* Use this component for longer or free-form user input.
*/
class TextAreaComponent extends TextFieldComponentBaseDirective {
constructor(_focusService, _idService) {
super(_idService, _focusService);
this._updateValueAndValidityTrigger.pipe(takeUntilDestroyed()).subscribe(() => {
if (this.control) {
this._required.next(FudisValidatorUtilities.required(this.control));
this._maxLength.next(FudisValidatorUtilities.maxLength(this.control));
this._minLength.next(FudisValidatorUtilities.minLength(this.control));
}
});
}
ngOnInit() {
this._setComponentId('text-area');
this._updateValueAndValidityTrigger.next();
this._setControlValueSubscription();
}
ngOnChanges(changes) {
if (changes.control?.currentValue !== changes.control?.previousValue) {
if (!changes.control?.firstChange) {
this._setControlValueSubscription();
}
this._subscription?.unsubscribe();
this._subscription = this.control.valueChanges
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => this._updateValueAndValidityTrigger.next());
}
if (!changes.nullControlOnEmptyString?.firstChange &&
changes.nullControlOnEmptyString?.currentValue !==
changes.nullControlOnEmptyString?.previousValue) {
this._setControlValueSubscription();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextAreaComponent, deps: [{ token: FudisFocusService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TextAreaComponent, isStandalone: true, selector: "fudis-text-area", inputs: { control: "control" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"fudis-text-area fudis-input-size__{{ size }}\">\n <fudis-label\n [for]=\"id\"\n [id]=\"id + '-label'\"\n [text]=\"label\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n >\n </fudis-label>\n <textarea\n #inputRef\n class=\"fudis-form-input fudis-text-area__input\"\n [formControl]=\"control\"\n (blur)=\"handleBlur.emit($event)\"\n (keyup)=\"handleKeyUp.emit($event)\"\n (focus)=\"onFocus($event)\"\n [id]=\"id\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.aria-invalid]=\"(control.invalid && control.touched) || null\"\n [attr.minLength]=\"_minLength | async\"\n [attr.maxLength]=\"_maxLength | async\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-disabled]=\"control.disabled || disabled || null\"\n [readonly]=\"control.disabled || disabled || null\"\n >\n </textarea>\n @if (!disableGuidance) {\n <fudis-guidance\n [inputLabel]=\"label\"\n [maxLength]=\"_maxLength | async\"\n [helpText]=\"helpText\"\n [for]=\"id\"\n [control]=\"control\"\n >\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n</div>\n", dependencies: [{ kind: "component", type: LabelComponent, selector: "fudis-label", inputs: ["id", "text", "for", "required"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: GuidanceComponent, selector: "fudis-guidance", inputs: ["for", "inputLabel", "control", "formGroup", "helpText", "maxLength", "selectedOption", "groupBlurredOut", "groupHelpTextHidden"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextAreaComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-text-area', changeDetection: ChangeDetectionStrategy.OnPush, imports: [LabelComponent, FormsModule, ReactiveFormsModule, GuidanceComponent, AsyncPipe], template: "<div class=\"fudis-text-area fudis-input-size__{{ size }}\">\n <fudis-label\n [for]=\"id\"\n [id]=\"id + '-label'\"\n [text]=\"label\"\n [required]=\"_required | async\"\n [popoverText]=\"popoverText\"\n [popoverPosition]=\"popoverPosition\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n >\n </fudis-label>\n <textarea\n #inputRef\n class=\"fudis-form-input fudis-text-area__input\"\n [formControl]=\"control\"\n (blur)=\"handleBlur.emit($event)\"\n (keyup)=\"handleKeyUp.emit($event)\"\n (focus)=\"onFocus($event)\"\n [id]=\"id\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.aria-invalid]=\"(control.invalid && control.touched) || null\"\n [attr.minLength]=\"_minLength | async\"\n [attr.maxLength]=\"_maxLength | async\"\n [attr.required]=\"(_required | async) || null\"\n [attr.aria-disabled]=\"control.disabled || disabled || null\"\n [readonly]=\"control.disabled || disabled || null\"\n >\n </textarea>\n @if (!disableGuidance) {\n <fudis-guidance\n [inputLabel]=\"label\"\n [maxLength]=\"_maxLength | async\"\n [helpText]=\"helpText\"\n [for]=\"id\"\n [control]=\"control\"\n >\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n</div>\n" }]
}], ctorParameters: () => [{ type: FudisFocusService }, { type: FudisIdService }], propDecorators: { control: [{
type: Input,
args: [{ required: true }]
}] } });
/**
* Allows entry of single-line text.
*
* Use this component for short textual input such as names or identifiers.
*/
class TextInputComponent extends TextFieldComponentBaseDirective {
constructor(_focusService, _idService) {
super(_idService, _focusService);
/**
* HTML type attribute: 'email' | 'number' | 'password' | 'tel' | 'text' | 'url'
*/
this.type = 'text';
/**
* Browser's automated assistance in filling out form field values, describing what input is
* expected from the user.
*/
this.autocomplete = 'off';
/**
* HTML name attribute (Note: in order to autocomplete to work properly, TextInput should have
* specified name attribute as well)
*/
this.name = null;
/**
* Step for number input: controls the minimum increment for number values. Use 'any' to allow any
* decimal value.
*/
this.step = null;
/**
* Max number for number input HTML attribute
*/
this._maxNumber = new BehaviorSubject(null);
/**
* Min number for number input HTML attribute
*/
this._minNumber = new BehaviorSubject(null);
this._updateValueAndValidityTrigger.pipe(takeUntilDestroyed()).subscribe(() => {
if (this.control) {
this._required.next(FudisValidatorUtilities.required(this.control));
if (this.type === 'number') {
this._maxLength.next(null);
this._minLength.next(null);
this._minNumber.next(FudisValidatorUtilities.min(this.control));
this._maxNumber.next(FudisValidatorUtilities.max(this.control));
}
else {
this._maxLength.next(FudisValidatorUtilities.maxLength(this.control));
this._minLength.next(FudisValidatorUtilities.minLength(this.control));
this._minNumber.next(null);
this._maxNumber.next(null);
}
}
});
}
ngOnInit() {
this._setComponentId('text-input');
this._updateValueAndValidityTrigger.next();
this._setControlValueSubscription();
}
ngOnChanges(changes) {
if (changes.control?.currentValue !== changes.control?.previousValue) {
if (!changes.control?.firstChange) {
this._setControlValueSubscription();
}
this._subscription?.unsubscribe();
this._subscription = this.control.valueChanges
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => this._updateValueAndValidityTrigger.next());
}
if (changes.type?.currentValue !== changes.type?.previousValue) {
this._updateValueAndValidityTrigger.next();
}
if (!changes.nullControlOnEmptyString?.firstChange &&
changes.nullControlOnEmptyString?.currentValue !==
changes.nullControlOnEmptyString?.previousValue) {
this._setControlValueSubscription();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextInputComponent, deps: [{ token: FudisFocusService }, { token: FudisIdService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: TextInputComponent, isStandalone: true, selector: "fudis-text-input", inputs: { control: "control", type: "type", autocomplete: "autocomplete", name: "name", step: "step" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"fudis-text-input fudis-input-size__{{ size }}\">\n <fudis-label\n [for]=\"id\"\n [id]=\"id + '-label'\"\n [text]=\"label\"\n [required]=\"_required | async\"\n [popoverPosition]=\"popoverPosition\"\n [popoverText]=\"popoverText\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n />\n <input\n #inputRef\n class=\"fudis-form-input fudis-text-input__input\"\n [id]=\"id\"\n [formControl]=\"control\"\n (blur)=\"handleBlur.emit($event)\"\n (keyup)=\"handleKeyUp.emit($event)\"\n (focus)=\"onFocus($event)\"\n [attr.step]=\"type === 'number' ? step : null\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.minlength]=\"_minLength | async\"\n [attr.maxlength]=\"_maxLength | async\"\n [attr.min]=\"_minNumber | async\"\n [attr.max]=\"_maxNumber | async\"\n [attr.name]=\"name\"\n [attr.required]=\"(_required | async) || null\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.type]=\"type\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.aria-disabled]=\"control.disabled || disabled || null\"\n [attr.aria-invalid]=\"(control.invalid && control.touched) || null\"\n [attr.aria-label]=\"ariaLabel\"\n [readonly]=\"control.disabled || disabled || null\"\n />\n @if (!disableGuidance) {\n <fudis-guidance\n [control]=\"control\"\n [for]=\"id\"\n [helpText]=\"helpText\"\n [inputLabel]=\"label\"\n [maxLength]=\"_maxLength | async\"\n >\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n</div>\n", dependencies: [{ kind: "component", type: LabelComponent, selector: "fudis-label", inputs: ["id", "text", "for", "required"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: GuidanceComponent, selector: "fudis-guidance", inputs: ["for", "inputLabel", "control", "formGroup", "helpText", "maxLength", "selectedOption", "groupBlurredOut", "groupHelpTextHidden"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TextInputComponent, decorators: [{
type: Component,
args: [{ selector: 'fudis-text-input', changeDetection: ChangeDetectionStrategy.OnPush, imports: [LabelComponent, FormsModule, ReactiveFormsModule, GuidanceComponent, AsyncPipe], template: "<div class=\"fudis-text-input fudis-input-size__{{ size }}\">\n <fudis-label\n [for]=\"id\"\n [id]=\"id + '-label'\"\n [text]=\"label\"\n [required]=\"_required | async\"\n [popoverPosition]=\"popoverPosition\"\n [popoverText]=\"popoverText\"\n [popoverTriggerLabel]=\"popoverTriggerLabel\"\n />\n <input\n #inputRef\n class=\"fudis-form-input fudis-text-input__input\"\n [id]=\"id\"\n [formControl]=\"control\"\n (blur)=\"handleBlur.emit($event)\"\n (keyup)=\"handleKeyUp.emit($event)\"\n (focus)=\"onFocus($event)\"\n [attr.step]=\"type === 'number' ? step : null\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.minlength]=\"_minLength | async\"\n [attr.maxlength]=\"_maxLength | async\"\n [attr.min]=\"_minNumber | async\"\n [attr.max]=\"_maxNumber | async\"\n [attr.name]=\"name\"\n [attr.required]=\"(_required | async) || null\"\n [attr.tabindex]=\"disabled ? -1 : null\"\n [attr.type]=\"type\"\n [attr.aria-describedby]=\"id + '_guidance'\"\n [attr.aria-disabled]=\"control.disabled || disabled || null\"\n [attr.aria-invalid]=\"(control.invalid && control.touched) || null\"\n [attr.aria-label]=\"ariaLabel\"\n [readonly]=\"control.disabled || disabled || null\"\n />\n @if (!disableGuidance) {\n <fudis-guidance\n [control]=\"control\"\n [for]=\"id\"\n [helpText]=\"helpText\"\n [inputLabel]=\"label\"\n [maxLength]=\"_maxLength | async\"\n >\n <ng-content select=\"fudis-error-message\" />\n </fudis-guidance>\n }\n</div>\n" }]
}], ctorParameters: () => [{ type: FudisFocusService }, { type: FudisIdService }], propDecorators: { control: [{
type: Input,
args: [{ required: true }]
}], type: [{
type: Input
}], autocomplete: [{
type: Input
}], name: [{
type: Input
}], step: [{
type: Input
}] } });
/**
* Marks an input as the start date of a date range.
*
* Use this directive to associate the control with a date range and enable correct validation
* behavior.
*/
class DateStartDirective {
constructor(_datepicker) {
this._datepicker = _datepicker;
_datepicker.dateRangeType = 'start';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DateStartDirective, deps: [{ token: DatepickerComponent, host: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: DateStartDirective, isStandalone: true, selector: "[fudisDateStart]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DateStartDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisDateStart]' }]
}], ctorParameters: () => [{ type: DatepickerComponent, decorators: [{
type: Host
}] }] });
/**
* Marks an input as the end date of a date range.
*
* Use this directive to associate the control with a date range and enable correct validation
* behavior.
*/
class DateEndDirective {
constructor(_datepicker) {
this._datepicker = _datepicker;
_datepicker.dateRangeType = 'end';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DateEndDirective, deps: [{ token: DatepickerComponent, host: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: DateEndDirective, isStandalone: true, selector: "[fudisDateEnd]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DateEndDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisDateEnd]' }]
}], ctorParameters: () => [{ type: DatepickerComponent, decorators: [{
type: Host
}] }] });
/**
* Displays a custom error message for a form field.
*
* Use this directive to link custom validation error to an input where existing validators are not
* possible to use.
*/
class ErrorMessageDirective {
constructor(_errorSummaryService, _translationService, _idService, _textInput, _textArea, _datePicker, _LocalizedTextGroup, _checkboxGroup, _select, _multiSelect, _radioButtonGroup) {
this._errorSummaryService = _errorSummaryService;
this._translationService = _translationService;
this._idService = _idService;
this._textInput = _textInput;
this._textArea = _textArea;
this._datePicker = _datePicker;
this._LocalizedTextGroup = _LocalizedTextGroup;
this._checkboxGroup = _checkboxGroup;
this._select = _select;
this._multiSelect = _multiSelect;
this._radioButtonGroup = _radioButtonGroup;
/**
* Output for handling a state when error is sent to Error Summary
*/
this.handleAddError = new EventEmitter();
/**
* Output for handling a state when error is removed from Error Summary
*/
this.handleRemoveError = new EventEmitter();
/**
* Boolean determining if Error Message is added
*/
this._errorAdded = false;
this._destroyRef = inject(DestroyRef);
if (_textInput) {
this._parent = _textInput;
}
else if (_textArea) {
this._parent = _textArea;
}
else if (_datePicker) {
this._parent = _datePicker;
}
else if (_radioButtonGroup) {
this._parent = _radioButtonGroup;
}
else if (_LocalizedTextGroup) {
this._parentGroup = _LocalizedTextGroup;
}
else if (_checkboxGroup) {
this._parentGroup = _checkboxGroup;
}
else if (_select) {
this._parent = _select;
}
else if (_multiSelect) {
this._parent = _multiSelect;
}
this._id = _idService.getNewId('error-message');
}
ngOnInit() {
if ((this._parent || this._parentGroup) && this.message) {
this._addControlValidator();
}
}
ngOnChanges(changes) {
const newMessage = changes.message?.currentValue;
if (newMessage !== changes.message?.previousValue) {
/**
* Update validator message if message is a string and not observable
*/
if (typeof newMessage === 'string') {
if (!this._messageStringAsObservable) {
this._messageStringAsObservable = new BehaviorSubject(newMessage);
this._customValidatorInstance = this._customControlValidatorFn(this._messageStringAsObservable);
}
else {
this._messageStringAsObservable.next(newMessage);
}
this._parent?.control?.updateValueAndValidity();
this._parentGroup?.formGroup?.updateValueAndValidity();
}
else if (newMessage) {
this._customValidatorInstance = this._customControlValidatorFn(newMessage);
newMessage.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => {
this._parent?.control?.updateValueAndValidity();
this._parentGroup?.formGroup?.updateValueAndValidity();
});
}
}
}
ngOnDestroy() {
this._removeValidator();
}
/**
* Create custom validator with error message
*/
_customControlValidatorFn(message) {
return (control) => {
if (!control) {
return null;
}
return { [this._id]: { message } };
};
}
/**
* Add created validator to the parent component control
*/
_addControlValidator() {
if (this._parent) {
this._errorAdded = true;
this._parent.control.addValidators(this._customValidatorInstance);
this._parent.control.updateValueAndValidity();
this.handleAddError.emit({ [this._id]: { message: this.message } });
}
else if (this._parentGroup) {
this._errorAdded = true;
this._parentGroup.formGroup.addValidators(this._customValidatorInstance);
this._parentGroup.formGroup.updateValueAndValidity();
this.handleAddError.emit({ [this._id]: { message: this.message } });
}
}
/**
* Remove created validator from parent component control
*/
_removeValidator() {
if (this._parent && this._customValidatorInstance && this._errorAdded) {
this._errorAdded = false;
this._parent.control.removeValidators(this._customValidatorInstance);
this._parent.control.updateValueAndValidity();
this.handleRemoveError.emit({ [this._id]: { message: this.message } });
}
else if (this._parentGroup && this._customValidatorInstance && this._errorAdded) {
this._errorAdded = false;
this._parentGroup.formGroup.removeValidators(this._customValidatorInstance);
this._parentGroup.formGroup.updateValueAndValidity();
this.handleRemoveError.emit({ [this._id]: { message: this.message } });
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ErrorMessageDirective, deps: [{ token: FudisInternalErrorSummaryService }, { token: FudisTranslationService }, { token: FudisIdService }, { token: TextInputComponent, host: true, optional: true }, { token: TextAreaComponent, host: true, optional: true }, { token: DatepickerComponent, host: true, optional: true }, { token: LocalizedTextGroupComponent, host: true, optional: true }, { token: CheckboxGroupComponent, host: true, optional: true }, { token: SelectComponent, host: true, optional: true }, { token: MultiselectComponent, host: true, optional: true }, { token: RadioButtonGroupComponent, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: ErrorMessageDirective, isStandalone: true, selector: "fudis-error-message", inputs: { message: "message" }, outputs: { handleAddError: "handleAddError", handleRemoveError: "handleRemoveError" }, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ErrorMessageDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-error-message' }]
}], ctorParameters: () => [{ type: FudisInternalErrorSummaryService }, { type: FudisTranslationService }, { type: FudisIdService }, { type: TextInputComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: TextAreaComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: DatepickerComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: LocalizedTextGroupComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: CheckboxGroupComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: SelectComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: MultiselectComponent, decorators: [{
type: Host
}, {
type: Optional
}] }, { type: RadioButtonGroupComponent, decorators: [{
type: Host
}, {
type: Optional
}] }], propDecorators: { message: [{
type: Input,
args: [{ required: true }]
}], handleAddError: [{
type: Output
}], handleRemoveError: [{
type: Output
}] } });
// TODO: Write tests
/**
* Marks an element as the primary form submission trigger. Must be applied on a `fudis-button` that
* is inside a `fudis-form`. On click, triggers error summary visibility. Pass
* `[formValid]="myForm.valid"` to control behavior.
*
* @example
* ```html
* <fudis-button fudisFormSubmit [label]="'Submit'" [formValid]="myForm.valid"></fudis-button>
* ```;
*/
class FormSubmitDirective {
constructor(_document, _button, _errorSummaryService) {
this._document = _document;
this._button = _button;
this._errorSummaryService = _errorSummaryService;
/**
* If false, Button will set parent Form's 'errorSummaryVisible' to true and call
* reloadFormErrors() from Error Summary Service If true, Button will set parent Form's
* 'errorSummaryVisible' to false
*/
this.formValid = false;
}
ngOnInit() {
this._button.type = 'submit';
}
/**
* Set parent Form's Error Summary Visible true and reload errors
*/
_onClick(event) {
const targetElement = event.target;
// Type guard
if (!(targetElement instanceof HTMLElement))
return;
/**
* Make sure that click event is from this submit Button and not any parent Form element
*/
const buttonElement = this._button.buttonEl.nativeElement;
const submitButton = this._document.activeElement === buttonElement || targetElement === buttonElement;
if (submitButton && !isButtonDisabled(buttonElement)) {
this._errorSummaryService
.getFormAncestorId(this._button.buttonEl.nativeElement)
.then((parentFormId) => {
if (parentFormId && !this.formValid) {
this._errorSummaryService.setErrorSummaryVisibility(parentFormId, true);
this._errorSummaryService.reloadFormErrors(parentFormId, true);
}
else if (parentFormId) {
this._errorSummaryService.setErrorSummaryVisibility(parentFormId, false);
}
});
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormSubmitDirective, deps: [{ token: DOCUMENT }, { token: ButtonComponent, host: true }, { token: FudisInternalErrorSummaryService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: FormSubmitDirective, isStandalone: true, selector: "[fudisFormSubmit]", inputs: { formValid: "formValid" }, host: { listeners: { "click": "_onClick($event)" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormSubmitDirective, decorators: [{
type: Directive,
args: [{ selector: '[fudisFormSubmit]' }]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: ButtonComponent, decorators: [{
type: Host
}] }, { type: FudisInternalErrorSummaryService }], propDecorators: { formValid: [{
type: Input
}], _onClick: [{
type: HostListener,
args: ['click', ['$event']]
}] } });
/**
* Identifies the actions area of a form.
*
* Use this directive to group submit or secondary form actions consistently.
*/
class FormActionsDirective {
constructor() {
this._hostClass = 'fudis-form-actions';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: FormActionsDirective, isStandalone: true, selector: "fudis-form-actions", host: { properties: { "class": "this._hostClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormActionsDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-form-actions' }]
}], propDecorators: { _hostClass: [{
type: HostBinding,
args: ['class']
}] } });
/**
* Identifies the header area of a form.
*
* Use this directive for additional information like intsructions for the form that should be
* grouped with the actual form heading.
*/
class FormHeaderDirective {
constructor() {
this._hostClass = 'fudis-form-header';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: FormHeaderDirective, isStandalone: true, selector: "fudis-form-header", host: { properties: { "class": "this._hostClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormHeaderDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-form-header' }]
}], propDecorators: { _hostClass: [{
type: HostBinding,
args: ['class']
}] } });
/**
* Identifies the main content area of a form.
*
* Use this directive for form fields.
*/
class FormContentDirective {
constructor() {
this._hostClass = 'fudis-form-content';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: FormContentDirective, isStandalone: true, selector: "fudis-form-content", host: { properties: { "class": "this._hostClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FormContentDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-form-content' }]
}], propDecorators: { _hostClass: [{
type: HostBinding,
args: ['class']
}] } });
/**
* Identifies the actions area of a section.
*
* Use this directive to add buttons that affect the section as a whole.
*/
class SectionActionsDirective {
constructor() {
this._hostClass = 'fudis-section-actions';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SectionActionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SectionActionsDirective, isStandalone: true, selector: "fudis-section-actions", host: { properties: { "class": "this._hostClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SectionActionsDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-section-actions' }]
}], propDecorators: { _hostClass: [{
type: HostBinding,
args: ['class']
}] } });
/**
* Identifies the main content area of a section.
*
* Use this directive to structure section content.
*/
class SectionContentDirective {
constructor() {
this._hostClass = 'fudis-section-content';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SectionContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SectionContentDirective, isStandalone: true, selector: "fudis-section-content", host: { properties: { "class": "this._hostClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SectionContentDirective, decorators: [{
type: Directive,
args: [{ selector: 'fudis-section-content' }]
}], propDecorators: { _hostClass: [{
type: HostBinding,
args: ['class']
}] } });
/**
* Error Summary Service public methods and tools
*/
class FudisErrorSummaryService {
constructor(_errorSummaryService) {
this._errorSummaryService = _errorSummaryService;
this._destroyRef = inject(DestroyRef);
/**
* To store Subscriptions for each sent Observable, so it can be unsubscribed on removeError()
*/
this._subscriptions = {};
}
/**
* To define ID for each subscription
*
* @param error With required properties
* @returns Generated id
*/
_getSubscriptionId(error) {
return `${error.formId}_${error.focusId}_${error.id}`;
}
/**
* Get current updateStrategy of Error Summary. By default 'reloadOnly'.
*/
get updateStrategy() {
return this._errorSummaryService.updateStrategy;
}
/**
* Set updateStrategy of Error Summary
*/
setUpdateStrategy(value) {
this._errorSummaryService.updateStrategy = value;
}
/**
* Reloads all Forms with Error Summary
*/
reloadAllErrors() {
this._errorSummaryService.focusToFormOnReload = null;
this._errorSummaryService.reloadAllErrors();
}
/**
* Reload a specific Form Id
*
* @param id Form Id
* @param focus Focus to Error Summary
*/
reloadFormErrors(id, focus = true) {
this._errorSummaryService.reloadFormErrors(id, focus);
}
// TODO: This should be removed and replaced with getter for collection of Signals per individual Forms
/**
* Returns an observable of all errors sent to Error Summary. Note, that Observable is updated
* only when reloadFormErrors is called.
*/
getErrorsObservable() {
return this._errorSummaryService.errorsObservable;
}
/**
* To add message to specific Form's Error Summary.
*
* @param id Identifier of the message, eg. 'app-custom-error-abc123'
* @param formId Id of Form component
* @param focusId HTML element's id, where user focus should be moved when user clicks the
* message.
* @param message Visible message to the user
*/
addError(id, formId, focusId, message) {
if (typeof message === 'string') {
const newError = { focusId, formId, message, id };
this._errorSummaryService.addError(newError);
}
else {
const subscriptionId = this._getSubscriptionId({ id, formId, focusId });
this._subscriptions[subscriptionId] = message
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe((newMessage) => {
const newError = { focusId, formId, message: newMessage, id };
this._errorSummaryService.addError(newError);
});
}
}
/**
* To remove messages from Form's Error Summary
*
* @param id Identifier of provided message in 'addError()'
* @param formId Id of Form component
* @param focusId HTML element's id, where user focus should be moved when user clicks the
* message.
*/
removeError(id, formId, focusId) {
const removeError = { focusId, formId, id };
const subscriptionId = this._getSubscriptionId(removeError);
if (this._subscriptions[subscriptionId]) {
this._subscriptions[subscriptionId].unsubscribe();
delete this._subscriptions[subscriptionId];
}
this._errorSummaryService.removeError(removeError);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisErrorSummaryService, deps: [{ token: FudisInternalErrorSummaryService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisErrorSummaryService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FudisErrorSummaryService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: FudisInternalErrorSummaryService }] });
/**
* Angular & ngMaterial
*/
class NgxFudisModule {
static forRoot() {
return {
ngModule: NgxFudisModule,
providers: [FudisFocusService, FudisGridService, FudisIdService, FudisTranslationService],
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NgxFudisModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: NgxFudisModule, imports: [AlertComponent,
AlertGroupComponent,
BadgeComponent,
BodyTextComponent,
BreadcrumbsComponent,
BreadcrumbsItemComponent,
ButtonComponent,
CheckboxComponent,
CheckboxGroupComponent,
CheckboxGroupOptionComponent,
CommonModule,
ControlComponentBaseDirective,
DatepickerComponent,
DateRangeComponent,
DateEndDirective,
DateStartDirective,
DescriptionListComponent,
DescriptionListItemComponent,
DescriptionListItemDetailsComponent,
DescriptionListItemTermComponent,
DialogActionsDirective,
DialogCloseDirective,
DialogComponent,
DialogContentDirective,
DialogTitleDirective,
DropdownBaseDirective,
DropdownItemBaseDirective,
DropdownMenuComponent,
DropdownMenuGroupComponent,
DropdownMenuItemComponent,
ErrorMessageDirective,
ErrorSummaryComponent,
ExpandableActionsDirective,
ExpandableComponent,
ExpandableContentDirective,
FieldSetComponent,
FieldsetActionsDirective,
FieldsetContentDirective,
FooterComponent,
FormActionsDirective,
FormCommonApiDirective,
FormComponent,
FormContentDirective,
FormHeaderDirective,
FormSubmitDirective,
FormsModule,
GridApiDirective,
GridComponent,
GridDirective,
GridItemComponent,
GridItemDirective,
GroupComponentBaseDirective,
GuidanceComponent,
HeadingComponent,
HorizontalRuleComponent,
IconButtonComponent,
IconComponent,
LabelComponent,
LanguageBadgeComponent,
LanguageBadgeGroupComponent,
LayoutModule,
LinkDirective,
LoadingSpinnerComponent,
LocalizedTextGroupComponent,
MatButtonModule,
MatDatepickerModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatNativeDateModule,
MultiselectAutocompleteDirective,
MultiselectChipListComponent,
MultiselectComponent,
MultiselectControlValueAccessorDirective,
MultiselectOptionComponent,
NotificationComponent,
PaginationComponent,
PopoverDirective,
RadioButtonComponent,
RadioButtonGroupComponent,
ReactiveFormsModule,
RouterModule,
ScrollingModule,
SectionActionsDirective,
SectionComponent,
SectionContentDirective,
SelectAutocompleteBaseDirective,
SelectAutocompleteDirective,
SelectBaseDirective,
SelectComponent,
SelectControlValueAccessorDirective,
SelectDropdownComponent,
SelectGroupComponent,
SelectIconsComponent,
SelectOptionBaseDirective,
SelectOptionComponent,
SelectOptionsDirective,
TabNavigationBarComponent,
TabNavigationPanelComponent,
TabNavigationTabComponent,
TextAreaComponent,
TextFieldComponentBaseDirective,
TextInputComponent,
ValidatorErrorMessageComponent], exports: [AlertComponent,
AlertGroupComponent,
BadgeComponent,
BodyTextComponent,
BreadcrumbsComponent,
BreadcrumbsItemComponent,
ButtonComponent,
CheckboxComponent,
CheckboxGroupComponent,
CheckboxGroupOptionComponent,
DatepickerComponent,
DateRangeComponent,
DateStartDirective,
DateEndDirective,
DescriptionListComponent,
DescriptionListItemComponent,
DescriptionListItemTermComponent,
DescriptionListItemDetailsComponent,
DialogComponent,
DialogTitleDirective,
DialogContentDirective,
DialogCloseDirective,
DialogActionsDirective,
DropdownMenuGroupComponent,
DropdownMenuComponent,
DropdownMenuItemComponent,
ErrorMessageDirective,
ExpandableComponent,
ExpandableActionsDirective,
ExpandableContentDirective,
FooterComponent,
FieldsetActionsDirective,
FieldsetContentDirective,
FieldSetComponent,
FormComponent,
FormActionsDirective,
FormContentDirective,
FormHeaderDirective,
FormSubmitDirective,
GridComponent,
GridDirective,
GridItemComponent,
GridItemDirective,
HeadingComponent,
HorizontalRuleComponent,
IconComponent,
IconButtonComponent,
LocalizedTextGroupComponent,
LanguageBadgeGroupComponent,
LinkDirective,
LoadingSpinnerComponent,
MultiselectComponent,
MultiselectOptionComponent,
NotificationComponent,
PaginationComponent,
PopoverDirective,
RadioButtonComponent,
RadioButtonGroupComponent,
SectionComponent,
SectionActionsDirective,
SectionContentDirective,
SelectComponent,
SelectOptionComponent,
SelectOptionsDirective,
SelectGroupComponent,
TabNavigationBarComponent,
TabNavigationPanelComponent,
TabNavigationTabComponent,
TextAreaComponent,
TextInputComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NgxFudisModule, providers: [
FudisInternalErrorSummaryService,
FudisDialogService,
FudisBreakpointService,
FudisErrorSummaryService,
FudisAlertService,
], imports: [AlertComponent,
AlertGroupComponent,
BodyTextComponent,
BreadcrumbsItemComponent,
ButtonComponent,
CheckboxComponent,
CheckboxGroupComponent,
CheckboxGroupOptionComponent,
CommonModule,
DatepickerComponent,
DescriptionListItemTermComponent,
DialogComponent,
ErrorSummaryComponent,
ExpandableComponent,
FieldSetComponent,
FormComponent,
FormsModule,
GuidanceComponent,
IconButtonComponent,
IconComponent,
LabelComponent,
LayoutModule,
LoadingSpinnerComponent,
LocalizedTextGroupComponent,
MatButtonModule,
MatDatepickerModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatNativeDateModule,
MultiselectChipListComponent,
MultiselectComponent,
MultiselectOptionComponent,
NotificationComponent,
PaginationComponent,
RadioButtonComponent,
RadioButtonGroupComponent,
ReactiveFormsModule,
RouterModule,
ScrollingModule,
SectionComponent,
SelectComponent,
SelectDropdownComponent,
SelectIconsComponent,
SelectOptionComponent,
TabNavigationBarComponent,
TextAreaComponent,
TextInputComponent] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: NgxFudisModule, decorators: [{
type: NgModule,
args: [{
/*
* Include imports outside of Fudis components in 'imports' array below, e.g. Angular Material components and other Angular tools.
* Also import Fudis standalone components here.
*/
imports: [
AlertComponent,
AlertGroupComponent,
BadgeComponent,
BodyTextComponent,
BreadcrumbsComponent,
BreadcrumbsItemComponent,
ButtonComponent,
CheckboxComponent,
CheckboxGroupComponent,
CheckboxGroupOptionComponent,
CommonModule,
ControlComponentBaseDirective,
DatepickerComponent,
DateRangeComponent,
DateEndDirective,
DateStartDirective,
DescriptionListComponent,
DescriptionListItemComponent,
DescriptionListItemDetailsComponent,
DescriptionListItemTermComponent,
DialogActionsDirective,
DialogCloseDirective,
DialogComponent,
DialogContentDirective,
DialogTitleDirective,
DropdownBaseDirective,
DropdownItemBaseDirective,
DropdownMenuComponent,
DropdownMenuGroupComponent,
DropdownMenuItemComponent,
ErrorMessageDirective,
ErrorSummaryComponent,
ExpandableActionsDirective,
ExpandableComponent,
ExpandableContentDirective,
FieldSetComponent,
FieldsetActionsDirective,
FieldsetContentDirective,
FooterComponent,
FormActionsDirective,
FormCommonApiDirective,
FormComponent,
FormContentDirective,
FormHeaderDirective,
FormSubmitDirective,
FormsModule,
GridApiDirective,
GridComponent,
GridDirective,
GridItemComponent,
GridItemDirective,
GroupComponentBaseDirective,
GuidanceComponent,
HeadingComponent,
HorizontalRuleComponent,
IconButtonComponent,
IconComponent,
LabelComponent,
LanguageBadgeComponent,
LanguageBadgeGroupComponent,
LayoutModule,
LinkDirective,
LoadingSpinnerComponent,
LocalizedTextGroupComponent,
MatButtonModule,
MatDatepickerModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatNativeDateModule,
MultiselectAutocompleteDirective,
MultiselectChipListComponent,
MultiselectComponent,
MultiselectControlValueAccessorDirective,
MultiselectOptionComponent,
NotificationComponent,
PaginationComponent,
PopoverDirective,
RadioButtonComponent,
RadioButtonGroupComponent,
ReactiveFormsModule,
RouterModule,
ScrollingModule,
SectionActionsDirective,
SectionComponent,
SectionContentDirective,
SelectAutocompleteBaseDirective,
SelectAutocompleteDirective,
SelectBaseDirective,
SelectComponent,
SelectControlValueAccessorDirective,
SelectDropdownComponent,
SelectGroupComponent,
SelectIconsComponent,
SelectOptionBaseDirective,
SelectOptionComponent,
SelectOptionsDirective,
TabNavigationBarComponent,
TabNavigationPanelComponent,
TabNavigationTabComponent,
TextAreaComponent,
TextFieldComponentBaseDirective,
TextInputComponent,
ValidatorErrorMessageComponent,
],
/*
* Add Fudis components, including all standalone components, in 'exports' array below.
* Export them in public-api.ts as well, when you want to expose them outside of the library.
*/
exports: [
AlertComponent,
AlertGroupComponent,
BadgeComponent,
BodyTextComponent,
BreadcrumbsComponent,
BreadcrumbsItemComponent,
ButtonComponent,
CheckboxComponent,
CheckboxGroupComponent,
CheckboxGroupOptionComponent,
DatepickerComponent,
DateRangeComponent,
DateStartDirective,
DateEndDirective,
DescriptionListComponent,
DescriptionListItemComponent,
DescriptionListItemTermComponent,
DescriptionListItemDetailsComponent,
DialogComponent,
DialogTitleDirective,
DialogContentDirective,
DialogCloseDirective,
DialogActionsDirective,
DropdownMenuGroupComponent,
DropdownMenuComponent,
DropdownMenuItemComponent,
ErrorMessageDirective,
ExpandableComponent,
ExpandableActionsDirective,
ExpandableContentDirective,
FooterComponent,
FieldsetActionsDirective,
FieldsetContentDirective,
FieldSetComponent,
FormComponent,
FormActionsDirective,
FormContentDirective,
FormHeaderDirective,
FormSubmitDirective,
GridComponent,
GridDirective,
GridItemComponent,
GridItemDirective,
HeadingComponent,
HorizontalRuleComponent,
IconComponent,
IconButtonComponent,
LocalizedTextGroupComponent,
LanguageBadgeGroupComponent,
LinkDirective,
LoadingSpinnerComponent,
MultiselectComponent,
MultiselectOptionComponent,
NotificationComponent,
PaginationComponent,
PopoverDirective,
RadioButtonComponent,
RadioButtonGroupComponent,
SectionComponent,
SectionActionsDirective,
SectionContentDirective,
SelectComponent,
SelectOptionComponent,
SelectOptionsDirective,
SelectGroupComponent,
TabNavigationBarComponent,
TabNavigationPanelComponent,
TabNavigationTabComponent,
TextAreaComponent,
TextInputComponent,
],
providers: [
FudisInternalErrorSummaryService,
FudisDialogService,
FudisBreakpointService,
FudisErrorSummaryService,
FudisAlertService,
],
}]
}] });
const fudisIconArray = [
'achievement',
'achievement-small',
'alert',
'alert-fill',
'alert-small',
'archive',
'arrow-big',
'arrow-dashed',
'arrow-solid',
'back',
'brochure',
'bullets',
'calendar',
'change-log',
'check',
'check-indeterminate-small',
'check-small',
'checkmark-circle',
'chevron',
'chevron-ring',
'chevron-ring-fill',
'clock',
'close',
'close-big',
'code',
'decrease-indent',
'delete',
'dot',
'edit',
'editor',
'exclamation-mark-circle',
'exclamation-mark-circle-fill',
'exclamation-mark-circle-small',
'eye',
'eye-blind',
'fail',
'hourglass',
'increase-indent',
'info',
'info-circle',
'info-circle-fill',
'info-circle-small',
'junction',
'link',
'list-add',
'list-minus',
'lock',
'lock-open',
'magic-wand',
'mail',
'menu',
'message',
'minus',
'minus-ring-fill',
'new-tab',
'notebook',
'notification',
'numbering',
'paperclip',
'pdf',
'people',
'person',
'person-small',
'picker',
'pin-small',
'place',
'place-ring-fill',
'plus',
'print',
'question-mark',
'question-mark-small',
'required',
'ring-close',
'ring-close-fill',
'ring-plus',
'ring-plus-fill',
'rosette',
'rule',
'search',
'seats',
'settings',
'shopping-cart',
'sorter',
'star',
'switch',
'three-dots',
'three-dots-small',
'waiting-approval',
'waiting-decline',
'zoom-in',
'zoom-out',
];
const fudisIconColorArray = [
'yellow',
'red',
'gray-dark',
'gray-light',
'primary',
'primary-dark',
'green',
'white',
];
const fudisIconRotateArray = ['flip-180', 'cw-90', 'ccw-90', 'none'];
/**
* File containing types used in FudisIdService
*/
const fudisIdComponents = [
'alert',
'autocomplete',
'body-text',
'button',
'checkbox',
'autocomplete-multi-select',
'datepicker',
'dialog',
'dropdown',
'error-message',
'expandable',
'fieldset',
'form',
'guidance',
'heading',
'localized-text-group',
'link',
'section',
'text-area',
'text-input',
'validator-error-message',
'pagination',
'popover',
];
const fudisIdParents = [
'breadcrumbs',
'checkbox-group',
'language-badge-group',
'radio-button-group',
];
const fudisIdGrandParents = [
'description-list',
'select',
'multiselect',
'dropdown-menu',
];
/**
* Alert
*/
const fudisAlertPositionArray = ['static', 'absolute', 'fixed'];
/**
* Badge
*/
const fudisBadgeVariantArray = [
'accent',
'danger',
'primary',
'secondary',
'success',
];
/**
* Button
*/
const fudisButtonVariantArray = ['primary', 'secondary', 'tertiary'];
const fudisButtonSizeArray = ['extra-small', 'small', 'medium'];
const fudisButtonTypeArray = ['submit', 'button'];
/**
* Dialog
*/
const fudisDialogSizeArray = ['xs', 'sm', 'md', 'lg', 'xl'];
/**
* Dropdown Menu
*/
const fudisDropdownMenuAlignArray = ['left', 'center', 'right'];
/**
* FieldsetActions
*/
const fudisFieldsetActionsAlignArray = ['start', 'below', 'end'];
/**
* Notification
*/
const fudisNotificationVariantArray = ['warning', 'danger', 'success', 'info'];
const fudisHeadingLevelArray = [1, 2, 3, 4, 5, 6];
const fudisHeadingVariantArray = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
const fudisBodyTextArray = [
'lg-regular',
'md-regular',
'sm-regular',
'lg-light',
'md-light',
];
const fudisTextAlignArray = ['left', 'right', 'center'];
/**
* Form Group Validators
*/
const FudisGroupValidators = {
oneRequired,
min: min$1,
max: max$1,
};
/**
* At least one option must be selected from a group
*/
function oneRequired(message) {
return (controlGroup) => {
const { controls } = controlGroup;
if (controls) {
const theOne = Object.keys(controls).find((key) => controls[key].value !== null &&
controls[key].value !== '' &&
controls[key].value !== false &&
controls[key].value !== undefined);
if (!theOne) {
return {
oneRequired: { message },
};
}
}
return null;
};
}
/**
* Minimum selected options of a group
*/
function min$1(settings) {
return (controlGroup) => {
const { controls } = controlGroup;
let amountOfSelected = 0;
if (controls) {
Object.keys(controls).forEach((key) => {
if (controls[key].value) {
amountOfSelected += 1;
}
});
if (amountOfSelected < settings.value) {
return {
min: { message: settings.message, value: settings.value },
};
}
}
return null;
};
}
/**
* Maximum selected options of a group
*/
function max$1(settings) {
return (controlGroup) => {
const { controls } = controlGroup;
let amountOfSelected = 0;
if (controls) {
Object.keys(controls).forEach((key) => {
if (controls[key].value) {
amountOfSelected += 1;
}
});
if (amountOfSelected > settings.value) {
return {
max: { message: settings.message, value: settings.value },
};
}
}
return null;
};
}
/**
* Form Control Validators
*/
const FudisValidators = {
required,
email,
maxLength,
minLength,
max,
min,
pattern,
datepickerMin,
datepickerMax,
};
/**
* Fudis version of Validators.required
*/
function required(message) {
return (control) => {
if (!Validators.required(control)) {
return null;
}
return { required: { message } };
};
}
/**
* Fudis version of Validators.email
*/
function email(message) {
return (control) => {
if (!Validators.email(control)) {
return null;
}
return { email: { message } };
};
}
/**
* Fudis version of Validators.maxLength
*/
function maxLength(length, message) {
return (control) => {
if (!Validators.maxLength(length)(control) || length < 1) {
return null;
}
return { maxlength: { message, requiredLength: length } };
};
}
/**
* Fudis version of Validators.minLength
*
* @param length
* @param message
* @param valueRequired By default this validator does not require, that control value cannot be
* empty. If set true, validator returns error, if control value is falsy.
*/
function minLength(length, message, valueRequired = false) {
return (control) => {
const requiredError = valueRequired && Validators.required(control);
if ((!Validators.minLength(length)(control) || length < 1) && !requiredError) {
return null;
}
return {
minlength: {
message,
requiredLength: length,
},
};
};
}
/**
* Fudis version of Validators.min
*/
function min(minValue, message) {
return (control) => {
if (!Validators.min(minValue)(control)) {
return null;
}
return {
min: {
message,
min: minValue,
},
};
};
}
/**
* Fudis version of Validators.max
*/
function max(maxValue, message) {
return (control) => {
if (!Validators.max(maxValue)(control)) {
return null;
}
return {
max: {
message,
max: maxValue,
},
};
};
}
/**
* Fudis version of Validators.pattern
*/
function pattern(regex, message) {
return (control) => {
if (!Validators.pattern(regex)(control)) {
return null;
}
return {
pattern: {
message,
},
};
};
}
/**
* Min date validator for Datepicker
*/
function datepickerMin(settings) {
return (control) => {
const simplifiedMinDate = settings.value.setHours(0, 0, 0, 0);
const simplifiedControlDate = control.value?.setHours(0, 0, 0, 0);
if (simplifiedMinDate > simplifiedControlDate) {
return {
datepickerMin: {
message: settings.message,
value: settings.value,
},
};
}
return null;
};
}
/**
* Max date validator for Datepicker
*/
function datepickerMax(settings) {
return (control) => {
const simplifiedMaxDate = settings.value.setHours(0, 0, 0, 0);
const simplifiedControlDate = control.value?.setHours(0, 0, 0, 0);
if (simplifiedMaxDate < simplifiedControlDate) {
return {
datepickerMax: {
message: settings.message,
value: settings.value,
},
};
}
return null;
};
}
/*
* Public API Surface of ngx-fudis
* Include component/directive/service/validator to the list below, when you want to expose it outside.
* If you create new type files, export them from the types/types.ts file. The types file is already exported from here.
*/
/**
* Generated bundle index. Do not edit.
*/
export { AlertComponent, AlertGroupComponent, BadgeComponent, BodyTextComponent, BreadcrumbsComponent, BreadcrumbsItemComponent, ButtonComponent, CheckboxComponent, CheckboxGroupComponent, CheckboxGroupOptionComponent, DateEndDirective, DateRangeComponent, DateStartDirective, DatepickerComponent, DescriptionListComponent, DescriptionListItemComponent, DescriptionListItemDetailsComponent, DescriptionListItemTermComponent, DialogActionsDirective, DialogCloseDirective, DialogComponent, DialogContentDirective, DialogTitleDirective, DropdownMenuComponent, DropdownMenuGroupComponent, DropdownMenuItemComponent, ErrorMessageDirective, ExpandableActionsDirective, ExpandableComponent, ExpandableContentDirective, FUDIS_DATE_FORMATS, FieldSetComponent, FieldsetActionsDirective, FieldsetContentDirective, FooterComponent, FormActionsDirective, FormComponent, FormContentDirective, FormHeaderDirective, FormSubmitDirective, FudisAlertService, FudisBreakpointService, FudisDateInputFormat, FudisDialogService, FudisErrorSummaryService, FudisGridService, FudisGroupValidators, FudisTranslationService, FudisValidators, GridComponent, GridDirective, GridItemComponent, GridItemDirective, HeadingComponent, HorizontalRuleComponent, IconButtonComponent, IconComponent, LanguageBadgeGroupComponent, LinkDirective, LoadingSpinnerComponent, LocalizedTextGroupComponent, MultiselectComponent, MultiselectOptionComponent, NgxFudisModule, NotificationComponent, PaginationComponent, PopoverDirective, RadioButtonComponent, RadioButtonGroupComponent, SectionActionsDirective, SectionComponent, SectionContentDirective, SelectComponent, SelectGroupComponent, SelectOptionComponent, SelectOptionsDirective, TabNavigationBarComponent, TabNavigationPanelComponent, TabNavigationTabComponent, TextAreaComponent, TextInputComponent, breakpointsMinWidthToObserve, defaultSpacingValue, fudisAlertPositionArray, fudisBadgeVariantArray, fudisBodyTextArray, fudisBreakpointsMinWidth, fudisButtonSizeArray, fudisButtonTypeArray, fudisButtonVariantArray, fudisDialogSizeArray, fudisDropdownMenuAlignArray, fudisFieldsetActionsAlignArray, fudisGridAlignArray, fudisGridAlignItemsArray, fudisGridWidthArray, fudisHeadingLevelArray, fudisHeadingVariantArray, fudisIconArray, fudisIconColorArray, fudisIconRotateArray, fudisIdComponents, fudisIdGrandParents, fudisIdParents, fudisInputSizeArray, fudisNotificationVariantArray, fudisSpacingArray, fudisSpacingValues, fudisTextAlignArray, gridColumnDefault, gridInputPropertyDefaults, gridItemDefault };
//# sourceMappingURL=funidata-ngx-fudis.mjs.map