1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

PM-13808 - Use new <bit-form-field> component in Excluded domains settings. (#13111)

* Use new <bit-form-field> component in Excluded domains settings.

Changing to new <bit-form-field> component necessitates modifying form to use ReactiveForms conventions. To handle this change, the deletable/uneditable domain state and new form control state are managed separately, including clearing the form state on save. Fields which compute vaules from the former state should now compute both values (see domain count); [formGroup], formArrayName, formControlName all necessary directives/properties on form and children.

* Disables margin; Removes OnInit in favor of protected member (fixes lint, and type checks).

* Ensure excludedDomain-based IDs are offset by the fieldsEditThreshold for form controls existing outside of stored values.

* Remove unnecessary chaining.
This commit is contained in:
Miles Blackwood
2025-02-19 17:11:40 -05:00
committed by GitHub
parent 3997a75741
commit 23ef1b1688
2 changed files with 62 additions and 35 deletions

View File

@@ -14,46 +14,52 @@
<bit-section *ngIf="!isLoading">
<bit-section-header>
<h2 bitTypography="h6">{{ "domainsTitle" | i18n }}</h2>
<span bitTypography="body2" slot="end">{{ excludedDomainsState?.length || 0 }}</span>
<span bitTypography="body2" slot="end">{{
excludedDomainsState.length + domainForms.value.length
}}</span>
</bit-section-header>
<ng-container *ngIf="excludedDomainsState">
<bit-item
*ngFor="let domain of excludedDomainsState; let i = index; trackBy: trackByFunction"
<bit-item
*ngFor="let domain of excludedDomainsState; let i = index; trackBy: trackByFunction"
>
<bit-item-content *ngIf="i < fieldsEditThreshold">
<div id="excludedDomain{{ i }}">{{ domain }}</div>
</bit-item-content>
<button
*ngIf="i < fieldsEditThreshold"
appA11yTitle="{{ 'remove' | i18n }}"
bitIconButton="bwi-minus-circle"
buttonType="danger"
size="small"
slot="end"
type="button"
(click)="removeDomain(i)"
></button>
</bit-item>
<form [formGroup]="domainListForm">
<bit-card
formArrayName="domains"
*ngFor="let domain of domainForms.controls; let i = index"
>
<bit-item-content>
<bit-label *ngIf="i >= fieldsEditThreshold">{{
"websiteItemLabel" | i18n: i + 1
}}</bit-label>
<bit-form-field disableMargin>
<bit-label>{{ "websiteItemLabel" | i18n: i + fieldsEditThreshold + 1 }}</bit-label>
<input
*ngIf="i >= fieldsEditThreshold"
bitInput
#uriInput
appInputVerbatim
bitInput
id="excludedDomain{{ i }}"
id="excludedDomain{{ i + fieldsEditThreshold }}"
inputmode="url"
name="excludedDomain{{ i }}"
name="excludedDomain{{ i + fieldsEditThreshold }}"
type="text"
(change)="fieldChange()"
[(ngModel)]="excludedDomainsState[i]"
formControlName="{{ i }}"
/>
<div id="excludedDomain{{ i }}" *ngIf="i < fieldsEditThreshold">{{ domain }}</div>
</bit-item-content>
<button
*ngIf="i < fieldsEditThreshold"
appA11yTitle="{{ 'remove' | i18n }}"
bitIconButton="bwi-minus-circle"
buttonType="danger"
size="small"
slot="end"
type="button"
(click)="removeDomain(i)"
></button>
</bit-item>
</ng-container>
<button bitLink class="tw-pt-2" type="button" linkType="primary" (click)="addNewDomain()">
<i class="bwi bwi-plus-circle bwi-fw" aria-hidden="true"></i> {{ "addDomain" | i18n }}
</button>
</bit-form-field>
</bit-card>
<button bitLink class="tw-pt-2" type="button" linkType="primary" (click)="addNewDomain()">
<i class="bwi bwi-plus-circle bwi-fw" aria-hidden="true"></i> {{ "addDomain" | i18n }}
</button>
</form>
</bit-section>
</div>
<popup-footer slot="footer">

View File

@@ -7,7 +7,13 @@ import {
AfterViewInit,
ViewChildren,
} from "@angular/core";
import { FormsModule } from "@angular/forms";
import {
FormsModule,
ReactiveFormsModule,
FormBuilder,
FormGroup,
FormArray,
} from "@angular/forms";
import { RouterModule } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
@@ -45,6 +51,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
CommonModule,
FormFieldModule,
FormsModule,
ReactiveFormsModule,
IconButtonModule,
ItemModule,
JslibModule,
@@ -68,6 +75,11 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
isLoading = false;
excludedDomainsState: string[] = [];
storedExcludedDomains: string[] = [];
protected domainListForm = new FormGroup({
domains: this.formBuilder.array([]),
});
// How many fields should be non-editable before editable fields
fieldsEditThreshold: number = 0;
@@ -77,10 +89,15 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
private domainSettingsService: DomainSettingsService,
private i18nService: I18nService,
private toastService: ToastService,
private formBuilder: FormBuilder,
) {
this.accountSwitcherEnabled = enableAccountSwitching();
}
get domainForms() {
return this.domainListForm.get("domains") as FormArray;
}
async ngAfterViewInit() {
this.domainSettingsService.neverDomains$
.pipe(takeUntil(this.destroy$))
@@ -117,8 +134,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
}
async addNewDomain() {
// add empty field to the Domains list interface
this.excludedDomainsState.push("");
this.domainForms.push(this.formBuilder.control(null));
await this.fieldChange();
}
@@ -148,7 +164,11 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
this.isLoading = true;
const newExcludedDomainsSaveState: NeverDomains = {};
const uniqueExcludedDomains = new Set(this.excludedDomainsState);
const uniqueExcludedDomains = new Set([
...this.excludedDomainsState,
...this.domainForms.value,
]);
for (const uri of uniqueExcludedDomains) {
if (uri && uri !== "") {
@@ -194,13 +214,14 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
title: "",
variant: "success",
});
this.domainForms.clear();
} catch {
this.toastService.showToast({
message: this.i18nService.t("unexpectedError"),
title: "",
variant: "error",
});
// Don't reset via `handleStateUpdate` to preserve input values
this.isLoading = false;
}