1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

[CL-707] Migrate CL codebase to signals (#15340)

This commit is contained in:
Vicki League
2025-07-16 08:39:37 -04:00
committed by GitHub
parent 97ec9a6339
commit 6811ea4c0b
124 changed files with 944 additions and 809 deletions

View File

@@ -1,23 +1,19 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, Input, booleanAttribute } from "@angular/core";
import { Component, booleanAttribute, input } from "@angular/core";
import { Option } from "./option";
import { MappedOptionComponent } from "./option";
@Component({
selector: "bit-option",
template: `<ng-template><ng-content></ng-content></ng-template>`,
})
export class OptionComponent<T = unknown> implements Option<T> {
@Input()
icon?: string;
export class OptionComponent<T = unknown> implements MappedOptionComponent<T> {
readonly icon = input<string>();
@Input({ required: true })
value: T;
readonly value = input.required<T>();
@Input({ required: true })
label: string;
readonly label = input.required<string>();
@Input({ transform: booleanAttribute })
disabled: boolean;
readonly disabled = input(undefined, { transform: booleanAttribute });
}

View File

@@ -1,6 +1,10 @@
import { MappedDataToSignal } from "../shared/data-to-signal-type";
export interface Option<T> {
icon?: string;
value: T | null;
label?: string;
disabled?: boolean;
}
export type MappedOptionComponent<T> = MappedDataToSignal<Option<T>>;

View File

@@ -1,9 +1,9 @@
<ng-select
[(ngModel)]="selectedOption"
[ngModel]="selectedOption()"
(ngModelChange)="onChange($event)"
[disabled]="disabled"
[placeholder]="placeholder"
[items]="items"
[placeholder]="placeholder()"
[items]="items()"
(blur)="onBlur()"
[labelForId]="labelForId"
[clearable]="false"

View File

@@ -37,15 +37,15 @@ describe("Select Component", () => {
describe("initial state", () => {
it("selected option should update when items input changes", () => {
expect(select.selectedOption?.value).toBeUndefined();
expect(select.selectedOption()?.value).toBeUndefined();
select.items = [
select.items.set([
{ label: "Apple", value: "apple" },
{ label: "Pear", value: "pear" },
{ label: "Banana", value: "banana" },
];
]);
expect(select.selectedOption?.value).toBe("apple");
expect(select.selectedOption()?.value).toBe("apple");
});
});
});

View File

@@ -13,6 +13,11 @@ import {
ViewChild,
Output,
EventEmitter,
input,
Signal,
computed,
model,
signal,
} from "@angular/core";
import {
ControlValueAccessor,
@@ -37,29 +42,23 @@ let nextId = 0;
templateUrl: "select.component.html",
providers: [{ provide: BitFormFieldControl, useExisting: SelectComponent }],
imports: [NgSelectModule, ReactiveFormsModule, FormsModule],
host: {
"[id]": "id()",
},
})
export class SelectComponent<T> implements BitFormFieldControl, ControlValueAccessor {
@ViewChild(NgSelectComponent) select: NgSelectComponent;
private _items: Option<T>[] = [];
/** Optional: Options can be provided using an array input or using `bit-option` */
@Input()
get items(): Option<T>[] {
return this._items;
}
set items(next: Option<T>[]) {
this._items = next;
this._selectedOption = this.findSelectedOption(next, this.selectedValue);
}
readonly items = model<Option<T>[] | undefined>();
@Input() placeholder = this.i18nService.t("selectPlaceholder");
readonly placeholder = input(this.i18nService.t("selectPlaceholder"));
@Output() closed = new EventEmitter();
protected selectedValue: T;
protected _selectedOption: Option<T>;
get selectedOption() {
return this._selectedOption;
}
protected selectedValue = signal<T>(undefined);
selectedOption: Signal<Option<T>> = computed(() =>
this.findSelectedOption(this.items(), this.selectedValue()),
);
protected searchInputId = `bit-select-search-input-${nextId++}`;
private notifyOnChange?: (value: T) => void;
@@ -79,7 +78,14 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
if (value == null || value.length == 0) {
return;
}
this.items = value.toArray();
this.items.set(
value.toArray().map((option) => ({
icon: option.icon(),
value: option.value(),
label: option.label(),
disabled: option.disabled(),
})),
);
}
@HostBinding("class") protected classes = ["tw-block", "tw-w-full", "tw-h-full"];
@@ -89,6 +95,8 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
get disabledAttr() {
return this.disabled || null;
}
// TODO: Skipped for signal migration because:
// Accessor inputs cannot be migrated as they are too complex.
@Input()
get disabled() {
return this._disabled ?? this.ngControl?.disabled ?? false;
@@ -100,8 +108,7 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
/**Implemented as part of NG_VALUE_ACCESSOR */
writeValue(obj: T): void {
this.selectedValue = obj;
this._selectedOption = this.findSelectedOption(this.items, this.selectedValue);
this.selectedValue.set(obj);
}
/**Implemented as part of NG_VALUE_ACCESSOR */
@@ -121,6 +128,8 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
/**Implemented as part of NG_VALUE_ACCESSOR */
protected onChange(option: Option<T> | null) {
this.selectedValue.set(option?.value);
if (!this.notifyOnChange) {
return;
}
@@ -154,9 +163,11 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
}
/**Implemented as part of BitFormFieldControl */
@HostBinding() @Input() id = `bit-multi-select-${nextId++}`;
readonly id = input(`bit-multi-select-${nextId++}`);
/**Implemented as part of BitFormFieldControl */
// TODO: Skipped for signal migration because:
// Accessor inputs cannot be migrated as they are too complex.
@HostBinding("attr.required")
@Input()
get required() {
@@ -178,8 +189,8 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
return [key, this.ngControl?.errors[key]];
}
private findSelectedOption(items: Option<T>[], value: T): Option<T> | undefined {
return items.find((item) => item.value === value);
private findSelectedOption(items: Option<T>[] | undefined, value: T): Option<T> | undefined {
return items?.find((item) => item.value === value);
}
/**Emits the closed event. */