UI: Refactoring liquid level widgets after review

This commit is contained in:
Vladyslav_Prykhodko 2023-10-24 15:08:16 +03:00
parent 73566db398
commit 0bab29c8ec
13 changed files with 138 additions and 150 deletions

View File

@ -84,7 +84,7 @@
label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape">
<tb-image-cards-select-option *ngFor="let shape of shapes"
[value]="shape"
[image]="createShapeLayout(shapesImageMap.get(shape), this.levelCardLayouts.simple)">
[image]="createShape(shapesImageMap.get(shape), this.levelCardLayouts.simple)">
{{ shapesTranslationMap.get(shape) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
@ -94,8 +94,7 @@
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
[required]="isRequired('shapeAttributeName')"
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
style="flex: 1; width: auto;"
asBoxInput colorClearButton class="flex"
style="flex: 1"
formControlName="shapeAttributeName">
</tb-string-autocomplete>
</div>
@ -118,7 +117,7 @@
<tb-image-cards-select-option
*ngFor="let layout of [levelCardLayouts.simple, levelCardLayouts.percentage, levelCardLayouts.absolute]"
[value]="layout"
[image]="createShapeLayout(shapesImageMap.get(levelCardWidgetConfigForm.get('selectedShape').value), layout)">
[image]="createShape(shapesImageMap.get(levelCardWidgetConfigForm.get('selectedShape').value), layout)">
{{ levelCardLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
@ -144,7 +143,7 @@
</mat-select>
</mat-form-field>
<tb-unit-input [fxShow]="levelCardWidgetConfigForm.get('widgetUnitsSource')?.value !== levelOptions.attribute"
asBoxInput colorClearButton class="flex"
class="flex"
[tagFilter]="unitsType.capacity"
[required]="isRequired('units')"
formControlName="units">
@ -153,8 +152,7 @@
[fetchOptionsFn]="fetchOptions.bind(this)"
[required]="isRequired('widgetUnitsAttributeName')"
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
style="flex: 1; width: auto;"
asBoxInput colorClearButton class="flex"
style="flex: 1"
formControlName="widgetUnitsAttributeName">
</tb-string-autocomplete>
</div>
@ -182,17 +180,14 @@
warning
</mat-icon>
</mat-form-field>
<tb-string-autocomplete style="max-width: 25%"
[fxShow]="levelCardWidgetConfigForm.get('volumeSource')?.value === levelOptions.attribute"
<tb-string-autocomplete [fxShow]="levelCardWidgetConfigForm.get('volumeSource')?.value === levelOptions.attribute"
[fetchOptionsFn]="fetchOptions.bind(this)"
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
[required]="isRequired('volumeAttributeName')"
style="flex: 1; width: auto;"
asBoxInput colorClearButton class="flex"
style="flex: 1"
formControlName="volumeAttributeName">
</tb-string-autocomplete>
<tb-unit-input asBoxInput colorClearButton
[tagFilter]="unitsType.capacity"
<tb-unit-input [tagFilter]="unitsType.capacity"
[required]="isRequired('volumeUnits')"
style="max-width: 25%" class="flex"
formControlName="volumeUnits">

View File

@ -43,23 +43,22 @@ import {
} from '@shared/models/widget-settings.models';
import {
CapacityUnits,
createAbsoluteLayout,
createPercentLayout,
createShapeLayout,
levelCardDefaultSettings,
LevelCardLayout,
levelCardLayoutTranslations,
LevelCardWidgetSettings,
LevelSelectOptions,
loadSvgShapesMapping,
optionsFilter,
Shapes,
shapesTranslations,
svgMapping
shapesTranslations
} from '@home/components/widget/lib/indicator/liquid-level-widget.models';
import { UnitsType } from '@shared/models/unit.models';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component';
import { map, publishReplay, refCount, tap } from 'rxjs/operators';
import { forkJoin, Observable, of } from 'rxjs';
import { map, share, tap } from 'rxjs/operators';
import { Observable, of, ReplaySubject } from 'rxjs';
import { ResourcesService } from '@core/services/resources.service';
import { UnitInputComponent } from '@shared/components/unit-input.component';
import { UtilsService } from '@core/services/utils.service';
@ -242,7 +241,7 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
actions: [configData.config.actions || {}, []]
});
this.levelCardWidgetConfigForm.get('selectedShape').valueChanges.subscribe((shape) => {
this.levelCardWidgetConfigForm.get('selectedShape').valueChanges.subscribe(() => {
this.cd.detectChanges();
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
});
@ -562,21 +561,8 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
}
private createSvgShapesMapping(): void {
const obsArray: Array<Observable<{svg: string; shape: Shapes}>> = [];
for (const shape of this.shapes) {
const svgUrl = svgMapping.get(shape).svg;
const obs = this.resourcesService.loadJsonResource<string>(svgUrl).pipe(
map((svg) => ({svg, shape}))
);
obsArray.push(obs);
}
forkJoin(obsArray).subscribe((svgData) => {
for (const data of svgData) {
this.shapesImageMap.set(data.shape, data.svg);
}
loadSvgShapesMapping(this.resourcesService).subscribe(shapeMap => {
this.shapesImageMap = shapeMap;
this.cd.detectChanges();
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
@ -584,25 +570,8 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
});
}
public createShapeLayout(svg: string, layout: LevelCardLayout): SafeUrl {
if (svg && layout) {
const parser = new DOMParser();
const svgImage = parser.parseFromString(svg, 'image/svg+xml');
if (layout === this.levelCardLayouts.simple) {
svgImage.querySelector('.container-overlay').remove();
} else if (layout === this.levelCardLayouts.percentage) {
svgImage.querySelector('.absolute-overlay').remove();
svgImage.querySelector('.percentage-value-container').innerHTML = createPercentLayout();
} else {
svgImage.querySelector('.absolute-value-container').innerHTML = createAbsoluteLayout();
svgImage.querySelector('.percentage-overlay').remove();
}
const encodedSvg = encodeURIComponent(svgImage.documentElement.outerHTML);
return this.sanitizer.bypassSecurityTrustResourceUrl(`data:image/svg+xml,${encodedSvg}`);
}
createShape(svg: string, layout: LevelCardLayout): SafeUrl {
return createShapeLayout(svg, layout, this.sanitizer);
}
public isRequired(formControlName: string): boolean {
@ -649,8 +618,12 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
fetchObservable = of([]);
}
return fetchObservable.pipe(
publishReplay(1),
refCount()
share({
connector: () => new ReplaySubject(1),
resetOnError: false,
resetOnComplete: false,
resetOnRefCountZero: false
})
);
}
}

View File

@ -25,6 +25,7 @@ import { AppState } from '@core/core.state';
import { AbstractControl, UntypedFormGroup } from '@angular/forms';
import { DataKey, DatasourceType, KeyInfo, WidgetConfigMode } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isDefinedAndNotNull } from '@core/utils';
export type WidgetConfigCallbacks = DatasourceCallbacks & WidgetActionCallbacks;

View File

@ -20,7 +20,9 @@ import { isDefined, isDefinedAndNotNull, isNumber, isString } from '@core/utils'
import {
CapacityUnits,
ConversionType,
convertLiters, createAbsoluteLayout, createPercentLayout,
convertLiters,
createAbsoluteLayout,
createPercentLayout,
extractValue,
levelCardDefaultSettings,
LevelCardLayout,
@ -40,11 +42,12 @@ import {
DateFormatProcessor,
inlineTextStyle
} from '@shared/models/widget-settings.models';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import { ResourcesService } from '@core/services/resources.service';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { Component, Input, OnInit } from '@angular/core';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
@Component({
selector: 'tb-liquid-level-widget',
template: ''
@ -328,7 +331,12 @@ export class LiquidLevelWidgetComponent implements OnInit {
this.updateLevel(newYPos, percentage);
}
private calculatePosition(percentage, limits): number {
private calculatePosition(percentage: number, limits: SvgLimits): number {
if (percentage > 100) {
return limits.max;
} if (percentage <= 0) {
return limits.min;
}
return limits.min + (percentage / 100) * (limits.max - limits.min);
}
@ -368,7 +376,7 @@ export class LiquidLevelWidgetComponent implements OnInit {
}
}
private updateShapeColor(value): void {
private updateShapeColor(value: number): void {
const shapeStrokes = this.ctx.$container.find('.tb-shape-stroke');
const shapeFill = this.ctx.$container.find('.tb-shape-fill');
this.tankColor.update(value);
@ -504,7 +512,7 @@ export class LiquidLevelWidgetComponent implements OnInit {
}
}
public cardClick($event) {
public cardClick($event: Event) {
this.ctx.actionsApi.cardClick($event);
}
}

View File

@ -24,12 +24,14 @@ import {
} from '@shared/models/widget-settings.models';
import { DataKey, WidgetConfig } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { Observable, of } from 'rxjs';
import { forkJoin, Observable, of } from 'rxjs';
import { singleEntityFilterFromDeviceId } from '@shared/models/query/query.models';
import { EntityType } from '@shared/models/entity-type.models';
import { catchError, mergeMap } from 'rxjs/operators';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { EntityService } from '@core/http/entity.service';
import { IAliasController } from '@core/api/widget-api.models';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ResourcesService } from '@core/services/resources.service';
export interface SvgInfo {
svg: string;
@ -123,7 +125,7 @@ export enum ConversionType {
from = 'from',
}
export const svgMapping = new Map<string, SvgInfo>(
export const svgMapping = new Map<Shapes, SvgInfo>(
[
[
Shapes.vOval,
@ -421,8 +423,49 @@ export const fetchEntityKeys = (entityAliasId: string, dataKeyTypes: Array<DataK
aliasInfo.entityFilter,
dataKeyTypes, [],
{ignoreLoading: true, ignoreErrors: true}
).pipe(
catchError(() => of([]))
)),
catchError(() => of([] as Array<DataKey>))
);
export const createShapeLayout = (svg: string, layout: LevelCardLayout, sanitizer: DomSanitizer): SafeUrl => {
if (svg && layout) {
const parser = new DOMParser();
const svgImage = parser.parseFromString(svg, 'image/svg+xml');
if (layout === LevelCardLayout.simple) {
svgImage.querySelector('.container-overlay').remove();
} else if (layout === LevelCardLayout.percentage) {
svgImage.querySelector('.absolute-overlay').remove();
svgImage.querySelector('.percentage-value-container').innerHTML = createPercentLayout();
} else {
svgImage.querySelector('.absolute-value-container').innerHTML = createAbsoluteLayout();
svgImage.querySelector('.percentage-overlay').remove();
}
const encodedSvg = encodeURIComponent(svgImage.documentElement.outerHTML);
return sanitizer.bypassSecurityTrustResourceUrl(`data:image/svg+xml,${encodedSvg}`);
}
};
export const loadSvgShapesMapping = (resourcesService: ResourcesService): Observable<Map<Shapes, string>> => {
const obsArray: Array<Observable<{svg: string; shape: Shapes}>> = [];
const shapesImageMap: Map<Shapes, string> = new Map();
svgMapping.forEach((value, shape) => {
const obs = resourcesService.loadJsonResource<string>(value.svg).pipe(
map((svg) => ({svg, shape}))
);
obsArray.push(obs);
});
return forkJoin(obsArray).pipe(
map(svgData => {
for (const data of svgData) {
shapesImageMap.set(data.shape, data.svg);
}
return shapesImageMap;
})
);
};

View File

@ -16,7 +16,7 @@
-->
<ng-container *ngIf="levelCardWidgetSettingsForm" [formGroup]="levelCardWidgetSettingsForm">
<div style="display: flex; flex-direction: column; gap: 16px;">
<div class="tb-form-panel no-padding no-border">
<div class="tb-form-panel">
<div fxFlex fxLayout="column" class="tb-form-row space-between">
<div fxFlex fxLayout="row" style="width: 100%;" fxLayoutAlign="space-between center">
@ -39,7 +39,7 @@
label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape">
<tb-image-cards-select-option *ngFor="let shape of shapes"
[value]="shape"
[image]="createShapeLayout(shapesImageMap.get(shape), this.levelCardLayouts.simple)">
[image]="createShape(shapesImageMap.get(shape), this.levelCardLayouts.simple)">
{{ shapesTranslationMap.get(shape) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
@ -49,8 +49,7 @@
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
[required]="isRequired('shapeAttributeName')"
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
style="flex: 1; width: auto;"
asBoxInput colorClearButton class="flex"
style="flex: 1"
formControlName="shapeAttributeName">
</tb-string-autocomplete>
</div>
@ -77,7 +76,7 @@
<tb-image-cards-select-option
*ngFor="let layout of [levelCardLayouts.simple, levelCardLayouts.percentage, levelCardLayouts.absolute]"
[value]="layout"
[image]="createShapeLayout(shapesImageMap.get(levelCardWidgetSettingsForm.get('selectedShape').value), layout)">
[image]="createShape(shapesImageMap.get(levelCardWidgetSettingsForm.get('selectedShape').value), layout)">
{{ levelCardLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
@ -103,7 +102,7 @@
</mat-select>
</mat-form-field>
<tb-unit-input [fxShow]="levelCardWidgetSettingsForm.get('widgetUnitsSource')?.value !== volumeOptions.attribute"
asBoxInput colorClearButton class="flex"
class="flex"
[tagFilter]="unitsType.capacity"
[required]="isRequired('units')"
formControlName="units">
@ -112,8 +111,7 @@
[fetchOptionsFn]="fetchOptions.bind(this)"
[required]="isRequired('widgetUnitsAttributeName')"
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
style="flex: 1; width: auto;"
asBoxInput colorClearButton class="flex"
style="flex: 1"
formControlName="widgetUnitsAttributeName">
</tb-string-autocomplete>
</div>
@ -141,17 +139,14 @@
warning
</mat-icon>
</mat-form-field>
<tb-string-autocomplete style="max-width: 25%"
[fxShow]="levelCardWidgetSettingsForm.get('volumeSource')?.value === volumeOptions.attribute"
<tb-string-autocomplete [fxShow]="levelCardWidgetSettingsForm.get('volumeSource')?.value === volumeOptions.attribute"
[fetchOptionsFn]="fetchOptions.bind(this)"
[required]="isRequired('volumeAttributeName')"
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
style="flex: 1; width: auto;"
asBoxInput colorClearButton class="flex"
style="flex: 1"
formControlName="volumeAttributeName">
</tb-string-autocomplete>
<tb-unit-input asBoxInput colorClearButton
[tagFilter]="unitsType.capacity"
<tb-unit-input [tagFilter]="unitsType.capacity"
[required]="isRequired('volumeUnits')"
style="max-width: 25%" class="flex"
formControlName="volumeUnits">

View File

@ -27,30 +27,26 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { formatValue, isDefined } from '@core/utils';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
import {
DateFormatProcessor,
DateFormatSettings
} from '@shared/models/widget-settings.models';
import {
CapacityUnits,
createShapeLayout,
fetchEntityKeys,
fetchEntityKeysForDevice,
levelCardDefaultSettings,
LevelCardLayout,
levelCardLayoutTranslations,
Shapes,
shapesTranslations,
svgMapping,
CapacityUnits,
LevelSelectOptions,
createPercentLayout,
createAbsoluteLayout,
loadSvgShapesMapping,
optionsFilter,
fetchEntityKeysForDevice,
fetchEntityKeys
Shapes,
shapesTranslations
} from '@home/components/widget/lib/indicator/liquid-level-widget.models';
import { UnitsType } from '@shared/models/unit.models';
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { forkJoin, Observable, of } from 'rxjs';
import { map, publishReplay, refCount, tap } from 'rxjs/operators';
import { Observable, of, ReplaySubject } from 'rxjs';
import { map, share, tap } from 'rxjs/operators';
import { ResourcesService } from '@core/services/resources.service';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { UtilsService } from '@core/services/utils.service';
@ -201,7 +197,7 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
});
this.levelCardWidgetSettingsForm.get('selectedShape').valueChanges.subscribe((shape) => {
this.levelCardWidgetSettingsForm.get('selectedShape').valueChanges.subscribe(() => {
this.cd.detectChanges();
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
});
@ -390,21 +386,8 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
}
private createSvgShapesMapping(): void {
const obsArray: Array<Observable<{svg: string; shape: Shapes}>> = [];
for (const shape of this.shapes) {
const svgUrl = svgMapping.get(shape).svg;
const obs = this.resourcesService.loadJsonResource<string>(svgUrl).pipe(
map((svg) => ({svg, shape}))
);
obsArray.push(obs);
}
forkJoin(obsArray).subscribe((svgData) => {
for (const data of svgData) {
this.shapesImageMap.set(data.shape, data.svg);
}
loadSvgShapesMapping(this.resourcesService).subscribe(shapeMap => {
this.shapesImageMap = shapeMap;
this.cd.detectChanges();
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
@ -412,25 +395,8 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
});
}
public createShapeLayout(svg: string, layout: LevelCardLayout): SafeUrl {
if (svg && layout) {
const parser = new DOMParser();
const svgImage = parser.parseFromString(svg, 'image/svg+xml');
if (layout === this.levelCardLayouts.simple) {
svgImage.querySelector('.container-overlay').remove();
} else if (layout === this.levelCardLayouts.percentage) {
svgImage.querySelector('.absolute-overlay').remove();
svgImage.querySelector('.percentage-value-container').innerHTML = createPercentLayout();
} else {
svgImage.querySelector('.absolute-value-container').innerHTML = createAbsoluteLayout();
svgImage.querySelector('.percentage-overlay').remove();
}
const encodedSvg = encodeURIComponent(svgImage.documentElement.outerHTML);
return this.sanitizer.bypassSecurityTrustResourceUrl(`data:image/svg+xml,${encodedSvg}`);
}
createShape(svg: string, layout: LevelCardLayout): SafeUrl {
return createShapeLayout(svg, layout, this.sanitizer);
}
public isRequired(formControlName: string): boolean {
@ -509,8 +475,12 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
fetchObservable = of([]);
}
return fetchObservable.pipe(
publishReplay(1),
refCount()
share({
connector: () => new ReplaySubject(1),
resetOnError: false,
resetOnComplete: false,
resetOnRefCountZero: false
})
);
}
}

View File

@ -32,11 +32,12 @@ import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { map, share, startWith, takeUntil } from 'rxjs/operators';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { SafeUrl } from '@angular/platform-browser';
export interface ImageCardsSelectOption {
name: string;
value: any;
image: string;
image: string | SafeUrl;
}
@Directive(
@ -49,7 +50,7 @@ export class ImageCardsSelectOptionDirective {
@Input() value: any;
@Input() image: string;
@Input() image: string | SafeUrl;
get viewValue(): string {
return (this._element?.nativeElement.textContent || '').trim();

View File

@ -64,7 +64,7 @@ import {
import { SignalStrengthWidgetComponent } from '@home/components/widget/lib/indicator/signal-strength-widget.component';
import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards/value-chart-card-widget.component';
import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component';
import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget';
import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget.component';
@NgModule({
declarations:

View File

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [appearance]="appearance" [ngClass]="ngClass"
<mat-form-field [appearance]="appearance" [class]="additionalClass"
[subscriptSizing]="subscriptSizing" style="width: 100%">
<mat-label *ngIf="label">{{label}}</mat-label>
<input matInput #nameInput [formControl]="selectionFormControl"

View File

@ -16,7 +16,7 @@
:host {
mat-form-field {
display: flex;
flex: 1 1 0%;
flex: 1;
max-width: 100%;
box-sizing: border-box;

View File

@ -33,6 +33,7 @@ import { Observable, of } from 'rxjs';
import { tap, map, switchMap, take } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { coerceBoolean } from '@shared/decorators/coercion';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
@Component({
selector: 'tb-string-autocomplete',
@ -48,41 +49,42 @@ import { coerceBoolean } from '@shared/decorators/coercion';
})
export class StringAutocompleteComponent implements ControlValueAccessor, OnInit {
@ViewChild('nameInput', {static: true}) nameInput: ElementRef;
@Input()
disabled: boolean;
@coerceBoolean()
@Input()
required: boolean = false;
@coerceBoolean()
required = false;
@Input() fetchOptionsFn: (searchText?: string) => Observable<Array<string>>;
@ViewChild('nameInput', {static: true}) nameInput: ElementRef;
@Input()
fetchOptionsFn: (searchText?: string) => Observable<Array<string>>;
@Input()
placeholderText: string = this.translate.instant('widget-config.set');
@Input()
subscriptSizing: string = 'dynamic';
subscriptSizing: SubscriptSizing = 'dynamic';
@Input()
ngClass: string | string[] | Set<string> | { [klass: string]: any; } = 'tb-inline-field tb-suffix-show-on-hover';
additionalClass: string | string[] | Record<string, boolean | undefined | null> = 'tb-inline-field tb-suffix-show-on-hover';
@Input()
appearance: string = 'outline';
appearance: MatFormFieldAppearance = 'outline';
@Input()
label: string;
@Input()
tooltipClass: string = 'tb-error-tooltip';
tooltipClass = 'tb-error-tooltip';
@Input()
errorText: string;
@coerceBoolean()
@Input()
showInlineError: boolean = false;
@coerceBoolean()
showInlineError = false;
selectionFormControl: FormControl;

View File

@ -62,9 +62,9 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit {
@Input()
disabled: boolean;
@coerceBoolean()
@Input()
required: boolean = false;
@coerceBoolean()
required = false;
@Input()
tagFilter: UnitsType;