From 901862a79d15c62ba1e72b2786f84d418733801b Mon Sep 17 00:00:00 2001 From: k-genov Date: Mon, 23 Dec 2024 15:05:31 +0100 Subject: [PATCH] input migration --- .../accordion/src/accordion-item.directive.ts | 14 +++----- .../accordion/src/accordion.component.html | 8 ++--- .../accordion/src/accordion.component.ts | 14 ++++---- .../checkbox/src/checkbox.component.ts | 15 +++++---- .../src/filter-field.component.html | 4 +-- .../src/filter-field.component.ts | 32 ++++++++----------- .../form-field/src/form-field.component.html | 2 +- .../form-field/src/form-field.component.ts | 16 ++++------ .../src/key-value-field.component.html | 12 +++---- .../src/key-value-field.component.ts | 20 +++++------- .../key-value/src/key-value.component.html | 2 +- .../key-value/src/key-value.component.ts | 16 ++++------ .../list/src/list-item.directive.ts | 19 ++++------- .../src/list-item/list-item.component.html | 10 +++--- .../list/src/list-item/list-item.component.ts | 15 ++++----- .../list/src/list.component.html | 8 ++--- .../list/src/list.component.ts | 15 ++++----- .../src/qualifier-chip-list.component.html | 6 ++-- .../src/qualifier-chip-list.component.ts | 18 ++++------- .../tabbar/src/tab.directive.ts | 17 ++++------ .../tabbar/src/tabbar.component.html | 4 +-- .../tabbar/src/tabbar.component.ts | 2 +- .../src/toggle-button.component.ts | 15 +++++---- .../sashbox/src/sash-initializer.directive.ts | 18 +++++------ .../components/sashbox/src/sash.directive.ts | 29 ++++++----------- .../sashbox/src/sashbox.component.ts | 17 +++++----- .../splitter/src/splitter.component.ts | 17 +++++----- .../throbber/src/throbber.component.html | 2 +- .../throbber/src/throbber.component.ts | 5 ++- .../viewport/src/viewport.component.spec.ts | 18 ++++++----- 30 files changed, 170 insertions(+), 220 deletions(-) diff --git a/projects/scion/components.internal/accordion/src/accordion-item.directive.ts b/projects/scion/components.internal/accordion/src/accordion-item.directive.ts index 4a15c1c7..1da4fce2 100644 --- a/projects/scion/components.internal/accordion/src/accordion-item.directive.ts +++ b/projects/scion/components.internal/accordion/src/accordion-item.directive.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Directive, Input, TemplateRef} from '@angular/core'; +import {Directive, input, TemplateRef} from '@angular/core'; /** * Use this directive to model an accordion item for {SciAccordionComponent}. @@ -37,26 +37,22 @@ export class SciAccordionItemDirective { /** * Optional key to identify this item and is used as key for the {TrackBy} function. */ - @Input() - public key?: string | undefined; + public readonly key = input(); /** * Provide template(s) to be rendered as actions of this list item. */ - @Input({required: true}) - public panel!: TemplateRef; + public readonly panel = input.required>(); /** * Indicates whether to expand this item. */ - @Input() - public expanded?: boolean | undefined; + public readonly expanded = input(); /** * Specifies CSS class(es) added to the
and elements, e.g. used for e2e testing. */ - @Input() - public cssClass?: string | string[] | undefined | null; + public readonly cssClass = input(); constructor(public readonly template: TemplateRef) { } diff --git a/projects/scion/components.internal/accordion/src/accordion.component.html b/projects/scion/components.internal/accordion/src/accordion.component.html index 473db62e..62a907a6 100644 --- a/projects/scion/components.internal/accordion/src/accordion.component.html +++ b/projects/scion/components.internal/accordion/src/accordion.component.html @@ -1,10 +1,10 @@ -
+
@for (item of items; track trackByFn($index, item)) {
+ [expanded]="item.expanded() ?? false">
diff --git a/projects/scion/components.internal/accordion/src/accordion.component.ts b/projects/scion/components.internal/accordion/src/accordion.component.ts index aadeb570..2da0dfbb 100644 --- a/projects/scion/components.internal/accordion/src/accordion.component.ts +++ b/projects/scion/components.internal/accordion/src/accordion.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {ChangeDetectorRef, Component, ContentChildren, ElementRef, HostBinding, Input, OnDestroy, OnInit, QueryList, SkipSelf, TrackByFunction, ViewChild} from '@angular/core'; +import {ChangeDetectorRef, Component, ContentChildren, ElementRef, HostBinding, input, OnDestroy, OnInit, QueryList, SkipSelf, TrackByFunction, ViewChild} from '@angular/core'; import {animate, AnimationMetadata, style, transition, trigger} from '@angular/animations'; import {SciAccordionItemDirective} from './accordion-item.directive'; import {CdkAccordion, CdkAccordionItem, CdkAccordionModule} from '@angular/cdk/accordion'; @@ -66,12 +66,12 @@ export class SciAccordionComponent implements OnInit, OnDestroy { @HostBinding('class.bubble') public get isBubbleVariant(): boolean { - return this.variant === 'bubble'; + return this.variant() === 'bubble'; } @HostBinding('class.solid') public get isSolidVariant(): boolean { - return this.variant === 'solid'; + return this.variant() === 'solid'; } @HostBinding('class.filled') @@ -83,14 +83,12 @@ export class SciAccordionComponent implements OnInit, OnDestroy { /** * Whether the accordion should allow multiple expanded accordion items simultaneously. */ - @Input() - public multi? = false; + public readonly multi = input(false); /** * Specifies the style of the accordion. */ - @Input() - public variant?: 'solid' | 'bubble' = 'bubble'; + public readonly variant = input<'solid' | 'bubble'>('bubble'); /** * Workaround for setting the filled state on initialization: @@ -104,7 +102,7 @@ export class SciAccordionComponent implements OnInit, OnDestroy { } public trackByFn: TrackByFunction = (index: number, item: SciAccordionItemDirective): any => { - return item.key ?? item; + return item.key() ?? item; }; public onToggle(item: CdkAccordionItem): void { diff --git a/projects/scion/components.internal/checkbox/src/checkbox.component.ts b/projects/scion/components.internal/checkbox/src/checkbox.component.ts index 7db38b47..2b864681 100644 --- a/projects/scion/components.internal/checkbox/src/checkbox.component.ts +++ b/projects/scion/components.internal/checkbox/src/checkbox.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input} from '@angular/core'; +import {booleanAttribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, forwardRef, input} from '@angular/core'; import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, ReactiveFormsModule} from '@angular/forms'; import {noop} from 'rxjs'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; @@ -31,17 +31,14 @@ import {UUID} from '@scion/toolkit/uuid'; }) export class SciCheckboxComponent implements ControlValueAccessor { + public readonly disabled = input(false, {transform: booleanAttribute}); + private _cvaChangeFn: (value: unknown) => void = noop; private _cvaTouchedFn: () => void = noop; protected formControl = new FormControl(false, {nonNullable: true}); protected id = UUID.randomUUID(); - @Input() - public set disabled(disabled: boolean | string | undefined | null) { - coerceBooleanProperty(disabled) ? this.formControl.disable() : this.formControl.enable(); - } - constructor(private _cd: ChangeDetectorRef) { this.formControl.valueChanges .pipe(takeUntilDestroyed()) @@ -49,6 +46,10 @@ export class SciCheckboxComponent implements ControlValueAccessor { this._cvaChangeFn(checked); this._cvaTouchedFn(); }); + + effect(() => { + this.disabled() ? this.formControl.disable() : this.formControl.enable(); + }); } public get isChecked(): boolean { @@ -73,7 +74,7 @@ export class SciCheckboxComponent implements ControlValueAccessor { * Method implemented as part of `ControlValueAccessor` to work with Angular forms API */ public setDisabledState(isDisabled: boolean): void { - this.disabled = isDisabled; + isDisabled ? this.formControl.disable() : this.formControl.enable(); this._cd.markForCheck(); } diff --git a/projects/scion/components.internal/filter-field/src/filter-field.component.html b/projects/scion/components.internal/filter-field/src/filter-field.component.html index 65f27d7d..082fec09 100644 --- a/projects/scion/components.internal/filter-field/src/filter-field.component.html +++ b/projects/scion/components.internal/filter-field/src/filter-field.component.html @@ -2,8 +2,8 @@ [attr.id]="id" autocomplete="off" [formControl]="formControl" - [tabindex]="tabindex ?? 0" - [placeholder]="placeholder ?? ''"> + [tabindex]="tabindex() ?? 0" + [placeholder]="placeholder() ?? ''"> diff --git a/projects/scion/components.internal/filter-field/src/filter-field.component.ts b/projects/scion/components.internal/filter-field/src/filter-field.component.ts index 9b7d5cb7..3e43f02a 100644 --- a/projects/scion/components.internal/filter-field/src/filter-field.component.ts +++ b/projects/scion/components.internal/filter-field/src/filter-field.component.ts @@ -8,11 +8,10 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, inject, Input, OnDestroy, Output, ViewChild} from '@angular/core'; +import {booleanAttribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, inject, input, OnDestroy, Output, ViewChild} from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR, NonNullableFormBuilder, ReactiveFormsModule} from '@angular/forms'; import {takeUntil} from 'rxjs/operators'; import {noop, Subject} from 'rxjs'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; import {UUID} from '@scion/toolkit/uuid'; import {SciMaterialIconDirective} from '@scion/components.internal/material-icon'; @@ -40,32 +39,23 @@ export class SciFilterFieldComponent implements ControlValueAccessor, OnDestroy private readonly _cd = inject(ChangeDetectorRef); private readonly _formBuilder = inject(NonNullableFormBuilder); - private _destroy$ = new Subject(); - private _cvaChangeFn: (value: any) => void = noop; - private _cvaTouchedFn: () => void = noop; - public id = UUID.randomUUID(); - /** * Sets focus order in sequential keyboard navigation. * If not specified, the focus order is according to the position in the document (tabindex=0). */ - @Input() - public tabindex?: number | undefined; + public readonly tabindex = input(); /** * Specifies the hint displayed when this field is empty. */ - @Input() - public placeholder?: string | undefined; + public readonly placeholder = input(); - @Input() - public set disabled(disabled: boolean | string | undefined | null) { - coerceBooleanProperty(disabled) ? this.formControl.disable() : this.formControl.enable(); - } + public readonly disabled = input(false, {transform: booleanAttribute}); - public get disabled(): boolean { - return this.formControl.disabled; - } + private _destroy$ = new Subject(); + private _cvaChangeFn: (value: any) => void = noop; + private _cvaTouchedFn: () => void = noop; + public id = UUID.randomUUID(); /** * Emits on filter change. @@ -102,6 +92,10 @@ export class SciFilterFieldComponent implements ControlValueAccessor, OnDestroy this._cvaTouchedFn(); // triggers form field validation and signals a blur event } }); + + effect(() => { + this.disabled() ? this.formControl.disable() : this.formControl.enable(); + }); } @HostListener('focus') @@ -157,7 +151,7 @@ export class SciFilterFieldComponent implements ControlValueAccessor, OnDestroy * @docs-private */ public setDisabledState(isDisabled: boolean): void { - this.disabled = isDisabled; + isDisabled ? this.formControl.disable() : this.formControl.enable(); this._cd.markForCheck(); } diff --git a/projects/scion/components.internal/form-field/src/form-field.component.html b/projects/scion/components.internal/form-field/src/form-field.component.html index f5b25b3a..1d25b201 100644 --- a/projects/scion/components.internal/form-field/src/form-field.component.html +++ b/projects/scion/components.internal/form-field/src/form-field.component.html @@ -1,4 +1,4 @@ - +
diff --git a/projects/scion/components.internal/form-field/src/form-field.component.ts b/projects/scion/components.internal/form-field/src/form-field.component.ts index a23fdfba..406e71c2 100644 --- a/projects/scion/components.internal/form-field/src/form-field.component.ts +++ b/projects/scion/components.internal/form-field/src/form-field.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Component, ElementRef, HostBinding, Input, OnDestroy} from '@angular/core'; +import {Component, ElementRef, HostBinding, input, OnDestroy} from '@angular/core'; import {ConfigurableFocusTrap, ConfigurableFocusTrapFactory} from '@angular/cdk/a11y'; import {UUID} from '@scion/toolkit/uuid'; @@ -19,21 +19,19 @@ import {UUID} from '@scion/toolkit/uuid'; }) export class SciFormFieldComponent implements OnDestroy { - private _focusTrap: ConfigurableFocusTrap; - public readonly id = UUID.randomUUID(); - @Input() - public direction: 'row' | 'column' = 'row'; + public readonly direction = input<'row' | 'column'>('row'); + + public readonly label = input.required(); + + private _focusTrap: ConfigurableFocusTrap; @HostBinding('class.column-direction') public get isColumnDirection(): boolean { - return this.direction === 'column'; + return this.direction() === 'column'; } - @Input({required: true}) - public label!: string; - constructor(host: ElementRef, focusTrapFactory: ConfigurableFocusTrapFactory) { this._focusTrap = focusTrapFactory.create(host.nativeElement); this._focusTrap.enabled = false; diff --git a/projects/scion/components.internal/key-value-field/src/key-value-field.component.html b/projects/scion/components.internal/key-value-field/src/key-value-field.component.html index 27f96a35..58cf4b78 100644 --- a/projects/scion/components.internal/key-value-field/src/key-value-field.component.html +++ b/projects/scion/components.internal/key-value-field/src/key-value-field.component.html @@ -1,18 +1,18 @@
- @if (title) { -

{{title}}

+ @if (title()) { +

{{title()}}

} - @if (addable) { + @if (addable()) { } - @if (removable) { + @if (removable()) { }
-@for (keyValueGroup of keyValueFormArray.controls; track keyValueGroup; let i = $index) { +@for (keyValueGroup of keyValueFormArray().controls; track keyValueGroup; let i = $index) { @if (keyValueGroup.controls.key.disabled) { @@ -24,7 +24,7 @@

{{title}}

- @if (removable) { + @if (removable()) { } } diff --git a/projects/scion/components.internal/key-value-field/src/key-value-field.component.ts b/projects/scion/components.internal/key-value-field/src/key-value-field.component.ts index 65336572..a842eed8 100644 --- a/projects/scion/components.internal/key-value-field/src/key-value-field.component.ts +++ b/projects/scion/components.internal/key-value-field/src/key-value-field.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Component, ElementRef, HostBinding, Input} from '@angular/core'; +import {Component, ElementRef, HostBinding, input} from '@angular/core'; import {FormArray, FormControl, FormGroup, NonNullableFormBuilder, ReactiveFormsModule} from '@angular/forms'; import {Dictionary, Maps} from '@scion/toolkit/util'; import {UUID} from '@scion/toolkit/uuid'; @@ -30,19 +30,15 @@ export class SciKeyValueFieldComponent { public readonly id = UUID.randomUUID(); - @Input() - public title?: string | undefined; + public readonly title = input(); - @Input({required: true}) - public keyValueFormArray!: FormArray>; + public readonly keyValueFormArray = input.required>>(); - @Input() @HostBinding('class.removable') - public removable = false; + public readonly removable = input(false); - @Input() @HostBinding('class.addable') - public addable = false; + public readonly addable = input(false); @HostBinding('attr.tabindex') public tabindex = -1; @@ -51,7 +47,7 @@ export class SciKeyValueFieldComponent { } public onRemove(index: number): void { - this.keyValueFormArray.removeAt(index); + this.keyValueFormArray().removeAt(index); // Focus the component to not lose the focus when the remove button is removed from the DOM. // Otherwise, if used in a popup, the popup would be closed because no element is focused anymore. @@ -59,14 +55,14 @@ export class SciKeyValueFieldComponent { } public onAdd(): void { - this.keyValueFormArray.push(this._formBuilder.group({ + this.keyValueFormArray().push(this._formBuilder.group({ key: this._formBuilder.control(''), value: this._formBuilder.control(''), })); } public onClear(): void { - this.keyValueFormArray.clear(); + this.keyValueFormArray().clear(); } /** diff --git a/projects/scion/components.internal/key-value/src/key-value.component.html b/projects/scion/components.internal/key-value/src/key-value.component.html index 165e4a16..72267a1d 100644 --- a/projects/scion/components.internal/key-value/src/key-value.component.html +++ b/projects/scion/components.internal/key-value/src/key-value.component.html @@ -1,4 +1,4 @@ -@for (property of flattenedProperties | keyvalue:keyCompareFn; track property) { +@for (property of flattenedProperties() | keyvalue:keyCompareFn; track property) { {{property.key}}: {{property.value}} } diff --git a/projects/scion/components.internal/key-value/src/key-value.component.ts b/projects/scion/components.internal/key-value/src/key-value.component.ts index 4eae3d74..130acf64 100644 --- a/projects/scion/components.internal/key-value/src/key-value.component.ts +++ b/projects/scion/components.internal/key-value/src/key-value.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, computed, input} from '@angular/core'; import {KeyValue, KeyValuePipe} from '@angular/common'; import {Dictionaries, Dictionary} from '@scion/toolkit/util'; @@ -26,20 +26,16 @@ import {Dictionaries, Dictionary} from '@scion/toolkit/util'; }) export class SciKeyValueComponent { - public flattenedProperties: Dictionary = {}; - private _keys: string[] = []; + public readonly object = input>(); - @Input() - public set object(object: Dictionary | Map) { - this.flattenedProperties = this.flattenObject(object || {}); - this._keys = Object.keys(this.flattenedProperties); - } + public flattenedProperties = computed(() => this.flattenObject(this.object() ?? {})); + private _keys = computed(() => Object.keys(this.flattenedProperties())); /** * Compares qualifier entries by their position in the object. */ - public keyCompareFn = (a: KeyValue, b: KeyValue): number => { - return this._keys.indexOf(a.key) - this._keys.indexOf(b.key); + public keyCompareFn = (a: KeyValue, b: KeyValue): number => { + return this._keys().indexOf(a.key) - this._keys().indexOf(b.key); }; private flattenObject(property: Dictionary | Map, path: string[] = []): Dictionary { diff --git a/projects/scion/components.internal/list/src/list-item.directive.ts b/projects/scion/components.internal/list/src/list-item.directive.ts index f2f17e7c..1f926a35 100644 --- a/projects/scion/components.internal/list/src/list-item.directive.ts +++ b/projects/scion/components.internal/list/src/list-item.directive.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Directive, Input, TemplateRef} from '@angular/core'; +import {Directive, input, TemplateRef} from '@angular/core'; /** * Use this directive to model a list item for {SciListComponent}. @@ -28,26 +28,19 @@ import {Directive, Input, TemplateRef} from '@angular/core'; @Directive({selector: 'ng-template[sciListItem]'}) export class SciListItemDirective { - private _actionTemplates: TemplateRef[] = []; - /** * Optional key to identify this item and is used to emit selection and internally as key for the {TrackBy} function. */ - @Input() - public key?: string | undefined; - + public readonly key = input(); /** * Provide template(s) to be rendered as actions of this list item. */ - @Input() - public set actions(actions: TemplateRef | TemplateRef[]) { - this._actionTemplates = (Array.isArray(actions) ? actions : (actions && [actions]) ?? []); - } + public readonly actions = input([], {transform: coalesceTemplateRefArray}); constructor(public readonly template: TemplateRef) { } +} - public get actionTemplates(): TemplateRef[] { - return this._actionTemplates; - } +function coalesceTemplateRefArray(value: TemplateRef | TemplateRef[] | undefined): TemplateRef[] { + return (Array.isArray(value) ? value : (value && [value]) ?? []); } diff --git a/projects/scion/components.internal/list/src/list-item/list-item.component.html b/projects/scion/components.internal/list/src/list-item/list-item.component.html index a625e172..26044bc5 100644 --- a/projects/scion/components.internal/list/src/list-item/list-item.component.html +++ b/projects/scion/components.internal/list/src/list-item/list-item.component.html @@ -1,21 +1,21 @@ @if (optionStyle) { - {{active ? 'radio_button_checked' : 'radio_button_unchecked'}} + {{active() ? 'radio_button_checked' : 'radio_button_unchecked'}} }
- +
-@if (listItem.actionTemplates.length) { +@if (listItem().actions().length) {
    - @for (actionTemplate of listItem.actionTemplates; track actionTemplate) { + @for (action of listItem().actions(); track action) {
  • - +
  • }
diff --git a/projects/scion/components.internal/list/src/list-item/list-item.component.ts b/projects/scion/components.internal/list/src/list-item/list-item.component.ts index ac5e731b..280e8fb5 100644 --- a/projects/scion/components.internal/list/src/list-item/list-item.component.ts +++ b/projects/scion/components.internal/list/src/list-item/list-item.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Component, ElementRef, HostBinding, Input} from '@angular/core'; +import {Component, ElementRef, HostBinding, input} from '@angular/core'; import {FocusableOption, FocusOrigin} from '@angular/cdk/a11y'; import {SciListItemDirective} from '../list-item.directive'; import {SciListStyle} from '../metadata'; @@ -26,15 +26,12 @@ import {SciMaterialIconDirective} from '@scion/components.internal/material-icon }) export class SciListItemComponent implements FocusableOption { - @Input({required: true}) - public listItem!: SciListItemDirective; + public readonly listItem = input.required(); - @HostBinding('class.active') - @Input() - public active = false; + public readonly style = input.required(); - @Input({required: true}) - public style!: SciListStyle; + @HostBinding('class.active') + public readonly active = input(false); @HostBinding('attr.tabindex') public tabindex = -1; @@ -51,6 +48,6 @@ export class SciListItemComponent implements FocusableOption { @HostBinding('class.option') public get optionStyle(): boolean { - return this.style === 'option-item'; + return this.style() === 'option-item'; } } diff --git a/projects/scion/components.internal/list/src/list.component.html b/projects/scion/components.internal/list/src/list.component.html index d0ee6060..2ad48739 100644 --- a/projects/scion/components.internal/list/src/list.component.html +++ b/projects/scion/components.internal/list/src/list.component.html @@ -1,4 +1,4 @@ -@if (filterPosition === 'top') { +@if (filterPosition() === 'top') { } @@ -7,17 +7,17 @@ @for (listItem of listItems; track trackByFn($index, listItem)) { } -@if (filterPosition === 'bottom') { +@if (filterPosition() === 'bottom') { } - + diff --git a/projects/scion/components.internal/list/src/list.component.ts b/projects/scion/components.internal/list/src/list.component.ts index 2f357c00..e6f51148 100644 --- a/projects/scion/components.internal/list/src/list.component.ts +++ b/projects/scion/components.internal/list/src/list.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {AfterViewInit, Component, ContentChildren, EventEmitter, HostBinding, HostListener, Input, OnDestroy, Output, QueryList, TrackByFunction, ViewChild, ViewChildren} from '@angular/core'; +import {AfterViewInit, Component, ContentChildren, EventEmitter, HostBinding, HostListener, input, OnDestroy, Output, QueryList, TrackByFunction, ViewChild, ViewChildren} from '@angular/core'; import {FocusKeyManager} from '@angular/cdk/a11y'; import {SciListItemDirective} from './list-item.directive'; import {SciListItemComponent} from './list-item/list-item.component'; @@ -88,21 +88,18 @@ export class SciListComponent implements AfterViewInit, OnDestroy { * Specifies where to position the filter field. * If not specified, does not display the filter field. */ - @Input() - public filterPosition?: 'top' | 'bottom' | undefined; + public readonly filterPosition = input<'top' | 'bottom'>(); /** * Specifies whether to render list items or option items. */ - @Input() - public style: SciListStyle = 'list-item'; + public readonly style = input('list-item'); /** * Sets focus order in sequential keyboard navigation. * If not specified, the focus order is according to the position in the document (tabindex=0). */ - @Input() - public tabindex?: number | undefined; + public readonly tabindex = input(); /** * Emits filter text on filter change. @@ -147,7 +144,7 @@ export class SciListComponent implements AfterViewInit, OnDestroy { takeUntil(this._destroy$), ) .subscribe((listItem: SciListItemDirective) => { - this.selection.emit(listItem.key); + this.selection.emit(listItem.key()); }); } @@ -169,7 +166,7 @@ export class SciListComponent implements AfterViewInit, OnDestroy { } public trackByFn: TrackByFunction = (index: number, item: SciListItemDirective): any => { - return item.key ?? item; + return item.key() ?? item; }; public ngOnDestroy(): void { diff --git a/projects/scion/components.internal/qualifier-chip-list/src/qualifier-chip-list.component.html b/projects/scion/components.internal/qualifier-chip-list/src/qualifier-chip-list.component.html index 239ed770..e84d5f42 100644 --- a/projects/scion/components.internal/qualifier-chip-list/src/qualifier-chip-list.component.html +++ b/projects/scion/components.internal/qualifier-chip-list/src/qualifier-chip-list.component.html @@ -1,10 +1,10 @@
    - @if (type) { + @if (type()) {
  • - {{type}} + {{type()}}
  • } - @for (entry of (qualifier ?? {}) | keyvalue:qualifierKeyCompareFn; track entry) { + @for (entry of (qualifier() ?? {}) | keyvalue:qualifierKeyCompareFn; track entry) {
  • {{entry.key}} {{entry.value}} diff --git a/projects/scion/components.internal/qualifier-chip-list/src/qualifier-chip-list.component.ts b/projects/scion/components.internal/qualifier-chip-list/src/qualifier-chip-list.component.ts index 27006631..568af912 100644 --- a/projects/scion/components.internal/qualifier-chip-list/src/qualifier-chip-list.component.ts +++ b/projects/scion/components.internal/qualifier-chip-list/src/qualifier-chip-list.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges} from '@angular/core'; +import {ChangeDetectionStrategy, Component, computed, input} from '@angular/core'; import {KeyValue, KeyValuePipe} from '@angular/common'; /** @@ -37,25 +37,19 @@ import {KeyValue, KeyValuePipe} from '@angular/common'; KeyValuePipe, ], }) -export class SciQualifierChipListComponent implements OnChanges { +export class SciQualifierChipListComponent { - private _qualifierKeys: string[] = []; + public readonly qualifier = input(); - @Input() - public qualifier?: Qualifier | undefined | null; + public readonly type = input(); - @Input() - public type?: string | undefined; - - public ngOnChanges(changes: SimpleChanges): void { - this._qualifierKeys = Object.keys(this.qualifier ?? {}); - } + private _qualifierKeys = computed(() => Object.keys(this.qualifier() ?? {})); /** * Compares qualifier entries by their position in the object. */ public qualifierKeyCompareFn = (a: KeyValue, b: KeyValue): number => { - return this._qualifierKeys.indexOf(a.key) - this._qualifierKeys.indexOf(b.key); + return this._qualifierKeys().indexOf(a.key) - this._qualifierKeys().indexOf(b.key); }; } diff --git a/projects/scion/components.internal/tabbar/src/tab.directive.ts b/projects/scion/components.internal/tabbar/src/tab.directive.ts index 3e776e24..adbec171 100644 --- a/projects/scion/components.internal/tabbar/src/tab.directive.ts +++ b/projects/scion/components.internal/tabbar/src/tab.directive.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Directive, Input, OnDestroy, TemplateRef, ViewContainerRef, ViewRef} from '@angular/core'; +import {Directive, input, OnDestroy, TemplateRef, ViewContainerRef, ViewRef} from '@angular/core'; /** * Use this directive to model a tab item for {SciTabbarComponent}. @@ -30,28 +30,25 @@ import {Directive, Input, OnDestroy, TemplateRef, ViewContainerRef, ViewRef} fro @Directive({selector: 'ng-template[sciTab]'}) export class SciTabDirective implements OnDestroy { - private _vcr: ViewContainerRef | undefined; - private _viewRef: ViewRef | undefined; - /** * Specifies the title of the tab. */ - @Input({required: true}) - public label!: string; + public readonly label = input.required(); /** * Specifies the identity of this tab. * * Can be used to activate this tab via {@link SciTabbarComponent.activateTab}. */ - @Input() - public name?: string | undefined; + public readonly name = input(); /** * Specifies CSS class(es) added to the tab item, e.g., to select the tab in end-to-end tests. */ - @Input() - public cssClass?: string | string[] | undefined | null; + public readonly cssClass = input(); + + private _vcr: ViewContainerRef | undefined; + private _viewRef: ViewRef | undefined; constructor(private readonly _templateRef: TemplateRef) { } diff --git a/projects/scion/components.internal/tabbar/src/tabbar.component.html b/projects/scion/components.internal/tabbar/src/tabbar.component.html index 7d0aadfd..608c7cad 100644 --- a/projects/scion/components.internal/tabbar/src/tabbar.component.html +++ b/projects/scion/components.internal/tabbar/src/tabbar.component.html @@ -2,9 +2,9 @@ @for (tab of tabs$ | async; track tab) { } diff --git a/projects/scion/components.internal/tabbar/src/tabbar.component.ts b/projects/scion/components.internal/tabbar/src/tabbar.component.ts index 85f01206..dc1675f2 100644 --- a/projects/scion/components.internal/tabbar/src/tabbar.component.ts +++ b/projects/scion/components.internal/tabbar/src/tabbar.component.ts @@ -103,7 +103,7 @@ export class SciTabbarComponent implements AfterContentInit { return undefined; } if (typeof tabOrIdentity === 'string') { - return this.tabs.find(it => it.name === tabOrIdentity) ?? undefined; + return this.tabs.find(it => it.name() === tabOrIdentity) ?? undefined; } return tabOrIdentity; } diff --git a/projects/scion/components.internal/toggle-button/src/toggle-button.component.ts b/projects/scion/components.internal/toggle-button/src/toggle-button.component.ts index a6a953d1..ee93ec94 100644 --- a/projects/scion/components.internal/toggle-button/src/toggle-button.component.ts +++ b/projects/scion/components.internal/toggle-button/src/toggle-button.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input} from '@angular/core'; +import {booleanAttribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, forwardRef, input} from '@angular/core'; import {UUID} from '@scion/toolkit/uuid'; import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, ReactiveFormsModule} from '@angular/forms'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; @@ -29,17 +29,14 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; }) export class SciToggleButtonComponent implements ControlValueAccessor { + public readonly disabled = input(false, {transform: booleanAttribute}); + private _cvaChangeFn: (value: unknown) => void = noop; private _cvaTouchedFn: () => void = noop; protected formControl = new FormControl(false, {nonNullable: true}); protected id = UUID.randomUUID(); - @Input() - public set disabled(disabled: boolean | string | undefined | null) { - coerceBooleanProperty(disabled) ? this.formControl.disable() : this.formControl.enable(); - } - constructor(private _cd: ChangeDetectorRef) { this.formControl.valueChanges .pipe(takeUntilDestroyed()) @@ -47,6 +44,10 @@ export class SciToggleButtonComponent implements ControlValueAccessor { this._cvaChangeFn(value); this._cvaTouchedFn(); }); + + effect(() => { + this.disabled() ? this.formControl.disable() : this.formControl.enable(); + }); } public get isToggled(): boolean { @@ -74,7 +75,7 @@ export class SciToggleButtonComponent implements ControlValueAccessor { * @docs-private */ public setDisabledState(isDisabled: boolean): void { - this.disabled = isDisabled; + isDisabled ? this.formControl.disable() : this.formControl.enable(); this._cd.markForCheck(); } diff --git a/projects/scion/components/sashbox/src/sash-initializer.directive.ts b/projects/scion/components/sashbox/src/sash-initializer.directive.ts index af811747..f0e24322 100644 --- a/projects/scion/components/sashbox/src/sash-initializer.directive.ts +++ b/projects/scion/components/sashbox/src/sash-initializer.directive.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Directive, ElementRef, Input, OnChanges, SimpleChanges} from '@angular/core'; +import {Directive, effect, ElementRef, inject, input} from '@angular/core'; import {SciSashDirective} from './sash.directive'; /** @@ -17,15 +17,15 @@ import {SciSashDirective} from './sash.directive'; @Directive({ selector: 'div[sciSashInitializer].sash', }) -export class SciSashInitializerDirective implements OnChanges { +export class SciSashInitializerDirective { - @Input({alias: 'sciSashInitializer', required: true}) - public sash!: SciSashDirective; + public readonly sash = input.required({alias: 'sciSashInitializer'}); - constructor(private _host: ElementRef) { - } - - public ngOnChanges(changes: SimpleChanges): void { - this.sash.element = this._host.nativeElement; + constructor() { + const host = inject(ElementRef).nativeElement as HTMLDivElement; + effect(() => { + const sash = this.sash(); + sash.element = host; + }); } } diff --git a/projects/scion/components/sashbox/src/sash.directive.ts b/projects/scion/components/sashbox/src/sash.directive.ts index 6aee8420..79559044 100644 --- a/projects/scion/components/sashbox/src/sash.directive.ts +++ b/projects/scion/components/sashbox/src/sash.directive.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Directive, Input, OnChanges, OnInit, SimpleChanges, TemplateRef} from '@angular/core'; +import {Directive, input, OnChanges, OnInit, SimpleChanges, TemplateRef} from '@angular/core'; import {SciSashBoxAccessor} from './sashbox-accessor'; import {BehaviorSubject, merge, Observable} from 'rxjs'; import {map, switchMap} from 'rxjs/operators'; @@ -38,24 +38,14 @@ import {map, switchMap} from 'rxjs/operators'; }) export class SciSashDirective implements OnInit, OnChanges { - private _size: string | number = '1'; - private _flexGrow$ = new BehaviorSubject(0); - /** * Specifies the sash size, either as fixed size with an explicit unit, - * or as a unitless proportion to distibute remaining space. A proportional + * or as a unitless proportion to distribute remaining space. A proportional * sash has the ability to grow or shrink if necessary, and must be >= 1. * * If not set, remaining space is distributed equally. */ - @Input() - public set size(size: string | number | null | undefined) { - this._size = size ?? '1'; - } - - public get size(): string | number { - return this._size; - } + public readonly size = input('1', {transform: (size: string | number | null | undefined): string | number => size ?? '1'}); /** * Specifies the minimal sash size in pixel or percent. @@ -63,8 +53,9 @@ export class SciSashDirective implements OnInit, OnChanges { * * If the unit is omitted, the value is interpreted as a pixel value. */ - @Input() - public minSize?: string | number | undefined; + public readonly minSize = input(); + + private _flexGrow$ = new BehaviorSubject(0); /** * @internal @@ -124,7 +115,7 @@ export class SciSashDirective implements OnInit, OnChanges { * @internal */ public get isFixedSize(): boolean { - return Number.isNaN(+this.size); + return Number.isNaN(+this.size()); } /** @@ -146,13 +137,13 @@ export class SciSashDirective implements OnInit, OnChanges { // fixed-sized sash this.flexGrow = 0; this.flexShrink = 0; - this.flexBasis = `${this.size}`; + this.flexBasis = `${this.size()}`; } else { // remaining space is distributed according to given proportion - const proportion = +this.size; + const proportion = +this.size(); if (proportion < 1) { - throw Error(`[IllegalSashSizeError] The proportion for flexible sized sashes must be >=1 [size=${this.size}]`); + throw Error(`[IllegalSashSizeError] The proportion for flexible sized sashes must be >=1 [size=${this.size()}]`); } this.flexGrow = proportion; diff --git a/projects/scion/components/sashbox/src/sashbox.component.ts b/projects/scion/components/sashbox/src/sashbox.component.ts index 5a7145eb..8d3b2bdc 100644 --- a/projects/scion/components/sashbox/src/sashbox.component.ts +++ b/projects/scion/components/sashbox/src/sashbox.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {Component, ContentChildren, ElementRef, EventEmitter, HostBinding, inject, Input, NgZone, OnDestroy, Output, QueryList} from '@angular/core'; +import {Component, ContentChildren, ElementRef, EventEmitter, HostBinding, inject, input, NgZone, OnDestroy, Output, QueryList} from '@angular/core'; import {startWith, takeUntil} from 'rxjs/operators'; import {BehaviorSubject, Observable, Subject} from 'rxjs'; import {SciSplitterComponent, SplitterMoveEvent} from '@scion/components/splitter'; @@ -107,8 +107,7 @@ export class SciSashboxComponent implements OnDestroy { /** * Specifies if to lay out sashes in a row (which is by default) or column arrangement. */ - @Input() - public direction: 'column' | 'row' = 'row'; + public readonly direction = input<'column' | 'row'>('row'); /** * Emits when start sashing. @@ -205,8 +204,10 @@ export class SciSashboxComponent implements OnDestroy { const sashSize1 = sash1.computedSize; const sashSize2 = sash2.computedSize; - const sashMinSize1 = sash1.minSize ? this.toPixel(sash1.minSize) : 0; - const sashMinSize2 = sash2.minSize ? this.toPixel(sash2.minSize) : 0; + const minSize = sash1.minSize(); + const sashMinSize1 = minSize ? this.toPixel(minSize) : 0; + const minSizeValue = sash2.minSize(); + const sashMinSize2 = minSizeValue ? this.toPixel(minSizeValue) : 0; const newSashSize1 = between(Math.round(sashSize1 + distance), {min: sashMinSize1, max: sashSize1 + sashSize2 - sashMinSize2}); const newSashSize2 = between(Math.round(sashSize2 - distance), {min: sashMinSize2, max: sashSize1 + sashSize2 - sashMinSize1}); @@ -245,12 +246,12 @@ export class SciSashboxComponent implements OnDestroy { @HostBinding('class.column') public get isColumnDirection(): boolean { - return this.direction === 'column'; + return this.direction() === 'column'; } @HostBinding('class.row') public get isRowDirection(): boolean { - return this.direction === 'row'; + return this.direction() === 'row'; } private get sashes(): SciSashDirective[] { @@ -296,7 +297,7 @@ function provideSashBoxAccessor(): SciSashBoxAccessor { return new class implements SciSashBoxAccessor { public get direction(): 'column' | 'row' { - return component.direction; + return component.direction(); } public get sashes$(): Observable { diff --git a/projects/scion/components/splitter/src/splitter.component.ts b/projects/scion/components/splitter/src/splitter.component.ts index 7fd905be..3c8774ec 100644 --- a/projects/scion/components/splitter/src/splitter.component.ts +++ b/projects/scion/components/splitter/src/splitter.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, Inject, Input, NgZone, OnDestroy, OnInit, Output, ViewChild} from '@angular/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, Inject, input, NgZone, OnDestroy, OnInit, Output, ViewChild} from '@angular/core'; import {fromEvent, merge, Subject} from 'rxjs'; import {DOCUMENT} from '@angular/common'; import {tapFirst} from '@scion/toolkit/operators'; @@ -63,6 +63,12 @@ import {first, takeUntil} from 'rxjs/operators'; }) export class SciSplitterComponent implements OnInit, OnDestroy { + /** + * Controls whether to render a vertical or horizontal splitter. + * By default, if not specified, renders a vertical splitter. + */ + public readonly orientation = input<'vertical' | 'horizontal'>('vertical'); + private _destroy$ = new Subject(); /* @docs-private */ @@ -76,16 +82,9 @@ export class SciSplitterComponent implements OnInit, OnDestroy { @HostBinding('class.horizontal') public get isHorizontal(): boolean { - return this.orientation === 'horizontal'; + return this.orientation() === 'horizontal'; } - /** - * Controls whether to render a vertical or horizontal splitter. - * By default, if not specified, renders a vertical splitter. - */ - @Input() - public orientation: 'vertical' | 'horizontal' = 'vertical'; - /** * Emits when starting to move the splitter. */ diff --git a/projects/scion/components/throbber/src/throbber.component.html b/projects/scion/components/throbber/src/throbber.component.html index 0af44c2d..21f8588e 100644 --- a/projects/scion/components/throbber/src/throbber.component.html +++ b/projects/scion/components/throbber/src/throbber.component.html @@ -1,4 +1,4 @@ -@switch (type) { +@switch (type()) { @case ('ellipsis') { } diff --git a/projects/scion/components/throbber/src/throbber.component.ts b/projects/scion/components/throbber/src/throbber.component.ts index 70fc44ef..7edf6331 100644 --- a/projects/scion/components/throbber/src/throbber.component.ts +++ b/projects/scion/components/throbber/src/throbber.component.ts @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, input} from '@angular/core'; import {SciEllipsisThrobberComponent} from './ellipsis-throbber/ellipsis-throbber.component'; import {SciRippleThrobberComponent} from './ripple-throbber/ripple-throbber.component'; import {SciRollerThrobberComponent} from './roller-throbber/roller-throbber.component'; @@ -66,6 +66,5 @@ export class SciThrobberComponent { * - **spinner** (default) * Represents a classic spinner throbber with strokes arranged radially. The strokes light up one after the other in clockwise direction and then then fade out again. */ - @Input() - public type: 'ellipsis' | 'ripple' | 'roller' | 'spinner' = 'spinner'; + public readonly type = input<'ellipsis' | 'ripple' | 'roller' | 'spinner'>('spinner'); } diff --git a/projects/scion/components/viewport/src/viewport.component.spec.ts b/projects/scion/components/viewport/src/viewport.component.spec.ts index c505b39d..86bbbfb6 100644 --- a/projects/scion/components/viewport/src/viewport.component.spec.ts +++ b/projects/scion/components/viewport/src/viewport.component.spec.ts @@ -9,7 +9,7 @@ */ import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {Component, ElementRef, HostBinding, Input, NgZone, Renderer2, viewChild, ViewChild} from '@angular/core'; +import {Component, ElementRef, HostBinding, input, NgZone, Renderer2, ViewChild, viewChild} from '@angular/core'; import {By} from '@angular/platform-browser'; import {Dictionary} from '@scion/toolkit/util'; import {SciViewportComponent} from './viewport.component'; @@ -23,10 +23,10 @@ describe('Viewport', () => { it('should show a vertical scrollbar on vertical overflow', async () => { const fixture = TestBed.createComponent(Testee1Component); + fixture.componentRef.setInput('direction', 'column'); fixture.autoDetectChanges(true); const component = fixture.componentInstance; - component.direction = 'column'; await flushChanges(fixture); expect(isScrollbarVisible(fixture, 'vertical')).withContext('(1) vertical)').toBeFalse(); expect(isScrollbarVisible(fixture, 'horizontal')).withContext('(1) horizonal)').toBeFalse(); @@ -84,10 +84,10 @@ describe('Viewport', () => { it('should show a horizontal scrollbar on horizontal overflow', async () => { const fixture = TestBed.createComponent(Testee1Component); + fixture.componentRef.setInput('direction', 'row'); fixture.autoDetectChanges(true); const component = fixture.componentInstance; - component.direction = 'row'; await flushChanges(fixture); expect(isScrollbarVisible(fixture, 'horizontal')).withContext('(1) horizonal)').toBeFalse(); expect(isScrollbarVisible(fixture, 'vertical')).withContext('(1) vertical)').toBeFalse(); @@ -1470,7 +1470,7 @@ describe('Viewport', () => { selector: 'spec-testee-1', template: ` -
    +
    @for (element of elements; track $index) { } @@ -1511,8 +1511,7 @@ class Testee1Component { public elements: null[] = []; - @Input({required: true}) - public direction!: 'row' | 'column'; + public readonly direction = input.required<'row' | 'column'>(); public onRemove(): void { this.elements = this.elements.slice(0, -1); @@ -1684,9 +1683,12 @@ class Testee3Component { }) class ElementDecimalSizeTestComponent { - @Input() + public readonly columnLayout = input(false); + @HostBinding('class.column-layout') - public columnLayout = false; + public get isColumnLayout(): boolean { + return this.columnLayout(); + } @ViewChild(SciViewportComponent, {static: true}) public viewportComponent!: SciViewportComponent;