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,6 +1,14 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { AfterContentChecked, Directive, ElementRef, Input, NgZone, Optional } from "@angular/core";
import {
AfterContentChecked,
booleanAttribute,
Directive,
ElementRef,
input,
NgZone,
Optional,
} from "@angular/core";
import { take } from "rxjs/operators";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -21,11 +29,7 @@ import { FocusableElement } from "../shared/focusable-element";
selector: "[appAutofocus], [bitAutofocus]",
})
export class AutofocusDirective implements AfterContentChecked {
@Input() set appAutofocus(condition: boolean | string) {
this.autofocus = condition === "" || condition === true;
}
private autofocus: boolean;
readonly appAutofocus = input(undefined, { transform: booleanAttribute });
// Track if we have already focused the element.
private focused = false;
@@ -46,7 +50,7 @@ export class AutofocusDirective implements AfterContentChecked {
*/
ngAfterContentChecked() {
// We only want to focus the element on initial render and it's not a mobile browser
if (this.focused || !this.autofocus || Utils.isMobileBrowser) {
if (this.focused || !this.appAutofocus() || Utils.isMobileBrowser) {
return;
}

View File

@@ -0,0 +1,21 @@
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
import * as stories from "./autofocus.stories";
<Meta of={stories} />
```ts
import { AutofocusDirective } from "@bitwarden/components";
```
<Title />
<Description />
<Primary />
<Controls />
## Accessibility
The autofocus directive has accessibility implications, because it will steal focus from wherever it
would naturally be placed on page load. Please consider whether or not the user truly needs the
element truly needs to be manually focused on their behalf.

View File

@@ -0,0 +1,26 @@
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { FormFieldModule } from "../form-field";
import { AutofocusDirective } from "./autofocus.directive";
export default {
title: "Component Library/Form/Autofocus Directive",
component: AutofocusDirective,
decorators: [
moduleMetadata({
imports: [AutofocusDirective, FormFieldModule],
}),
],
} as Meta;
export const AutofocusField: StoryObj = {
render: (args) => ({
template: /*html*/ `
<bit-form-field>
<bit-label>Email</bit-label>
<input bitInput formControlName="email" appAutofocus />
</bit-form-field>
`,
}),
};

View File

@@ -9,6 +9,8 @@ import {
NgZone,
Optional,
Self,
input,
model,
} from "@angular/core";
import { NgControl, Validators } from "@angular/forms";
@@ -30,9 +32,15 @@ export function inputBorderClasses(error: boolean) {
@Directive({
selector: "input[bitInput], select[bitInput], textarea[bitInput]",
providers: [{ provide: BitFormFieldControl, useExisting: BitInputDirective }],
host: {
"[class]": "classList()",
"[id]": "id()",
"[attr.type]": "type()",
"[attr.spellcheck]": "spellcheck()",
},
})
export class BitInputDirective implements BitFormFieldControl {
@HostBinding("class") @Input() get classList() {
classList() {
const classes = [
"tw-block",
"tw-w-full",
@@ -52,7 +60,7 @@ export class BitInputDirective implements BitFormFieldControl {
return classes.filter((s) => s != "");
}
@HostBinding() @Input() id = `bit-input-${nextId++}`;
readonly id = input(`bit-input-${nextId++}`);
@HostBinding("attr.aria-describedby") ariaDescribedBy: string;
@@ -60,10 +68,12 @@ export class BitInputDirective implements BitFormFieldControl {
return this.hasError ? true : undefined;
}
@HostBinding("attr.type") @Input() type?: InputTypes;
readonly type = model<InputTypes>();
@HostBinding("attr.spellcheck") @Input() spellcheck?: boolean;
readonly spellcheck = model<boolean>();
// TODO: Skipped for signal migration because:
// Accessor inputs cannot be migrated as they are too complex.
@HostBinding()
@Input()
get required() {
@@ -74,13 +84,13 @@ export class BitInputDirective implements BitFormFieldControl {
}
private _required: boolean;
@Input() hasPrefix = false;
@Input() hasSuffix = false;
readonly hasPrefix = input(false);
readonly hasSuffix = input(false);
@Input() showErrorsWhenDisabled? = false;
readonly showErrorsWhenDisabled = input<boolean>(false);
get labelForId(): string {
return this.id;
return this.id();
}
@HostListener("input")
@@ -89,7 +99,7 @@ export class BitInputDirective implements BitFormFieldControl {
}
get hasError() {
if (this.showErrorsWhenDisabled) {
if (this.showErrorsWhenDisabled()) {
return (
(this.ngControl?.status === "INVALID" || this.ngControl?.status === "DISABLED") &&
this.ngControl?.touched &&