1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

Use bit-select (+ReactiveForms) in Autofill on page load settings. (#13593)

* Use bit-select (+ReactiveForms) in Autofill on page load settings.

* Use ReactiveForms for Additional options.

* Disable margin reinclude from rebase.
This commit is contained in:
Miles Blackwood
2025-03-31 12:36:52 -04:00
committed by GitHub
parent 4e413283a8
commit 80e58427fb
2 changed files with 219 additions and 138 deletions

View File

@@ -154,121 +154,115 @@
</bit-item> </bit-item>
</bit-section> </bit-section>
<bit-section> <bit-section>
<bit-section-header> <form [formGroup]="autofillOnPageLoadForm">
<h2 bitTypography="h6">{{ "enableAutoFillOnPageLoadSectionTitle" | i18n }}</h2> <bit-section-header>
</bit-section-header> <legend>
<bit-card> <h2 bitTypography="h6">{{ "enableAutoFillOnPageLoadSectionTitle" | i18n }}</h2>
<bit-hint class="tw-mb-6 tw-text-sm"> </legend>
{{ "enableAutoFillOnPageLoadDesc" | i18n }} </bit-section-header>
<span <bit-card>
><b>{{ "warningCapitalized" | i18n }}</b <bit-hint class="tw-mb-6 tw-text-sm">
>: {{ "experimentalFeature" | i18n }}</span {{ "enableAutoFillOnPageLoadDesc" | i18n }}
> <span
<a ><b>{{ "warningCapitalized" | i18n }}</b
bitLink >: {{ "experimentalFeature" | i18n }}</span
class="tw-no-underline" >
href="https://bitwarden.com/help/auto-fill-browser/" <a
rel="noreferrer" bitLink
target="_blank" class="tw-no-underline"
> href="https://bitwarden.com/help/auto-fill-browser/"
{{ "learnMoreAboutAutofillOnPageLoadLinkText" | i18n }} rel="noreferrer"
</a> target="_blank"
</bit-hint> >
<bit-form-control> {{ "learnMoreAboutAutofillOnPageLoadLinkText" | i18n }}
<input </a>
bitCheckbox
id="autofillOnPageLoad"
type="checkbox"
(change)="updateAutofillOnPageLoad()"
[(ngModel)]="enableAutofillOnPageLoad"
[disabled]="autofillOnPageLoadFromPolicy$ | async"
/>
<bit-label for="autofillOnPageLoad">{{ "enableAutoFillOnPageLoad" | i18n }}</bit-label>
<bit-hint class="tw-text-sm" *ngIf="autofillOnPageLoadFromPolicy$ | async">{{
"enterprisePolicyRequirementsApplied" | i18n
}}</bit-hint>
</bit-form-control>
<bit-form-field disableMargin>
<bit-label for="defaultAutofill">{{ "defaultAutoFillOnPageLoad" | i18n }}</bit-label>
<select
bitInput
id="defaultAutofill"
(change)="updateAutofillOnPageLoadDefault()"
[(ngModel)]="autofillOnPageLoadDefault"
[disabled]="!enableAutofillOnPageLoad"
>
<option
*ngFor="let o of autofillOnPageLoadOptions"
[ngValue]="o.value"
[label]="o.name"
></option>
</select>
<bit-hint class="tw-text-sm">
{{ "defaultAutoFillOnPageLoadDesc" | i18n }}
</bit-hint> </bit-hint>
</bit-form-field> <bit-form-control>
</bit-card> <input
formControlName="autofillOnPageLoad"
bitCheckbox
id="autofillOnPageLoad"
type="checkbox"
/>
<bit-label for="autofillOnPageLoad">{{ "enableAutoFillOnPageLoad" | i18n }}</bit-label>
<bit-hint class="tw-text-sm" *ngIf="autofillOnPageLoadFromPolicy$ | async">{{
"enterprisePolicyRequirementsApplied" | i18n
}}</bit-hint>
</bit-form-control>
<bit-form-field disableMargin>
<bit-label for="defaultAutofill">{{ "defaultAutoFillOnPageLoad" | i18n }}</bit-label>
<bit-select formControlName="defaultAutofill" bitInput id="defaultAutofill">
<bit-option
*ngFor="let option of autofillOnPageLoadOptions"
[label]="option.name"
[value]="option.value"
>
</bit-option>
</bit-select>
<bit-hint class="tw-text-sm">
{{ "defaultAutoFillOnPageLoadDesc" | i18n }}
</bit-hint>
</bit-form-field>
</bit-card>
</form>
</bit-section> </bit-section>
<bit-section [disableMargin]="!blockBrowserInjectionsByDomainEnabled"> <bit-section [disableMargin]="!blockBrowserInjectionsByDomainEnabled">
<bit-section-header> <form [formGroup]="additionalOptionsForm">
<h2 bitTypography="h6">{{ "additionalOptions" | i18n }}</h2> <bit-section-header>
</bit-section-header> <h2 bitTypography="h6">{{ "additionalOptions" | i18n }}</h2>
<bit-card> </bit-section-header>
<bit-form-control> <bit-card>
<input <bit-form-control>
bitCheckbox <input
id="context-menu" formControlName="enableContextMenuItem"
type="checkbox" bitCheckbox
(change)="updateContextMenuItem()" id="context-menu"
[(ngModel)]="enableContextMenuItem" type="checkbox"
/> />
<bit-label for="context-menu">{{ "enableContextMenuItem" | i18n }}</bit-label> <bit-label for="context-menu">{{ "enableContextMenuItem" | i18n }}</bit-label>
</bit-form-control> </bit-form-control>
<bit-form-control> <bit-form-control>
<input <input formControlName="enableAutoTotpCopy" bitCheckbox id="totp" type="checkbox" />
bitCheckbox <bit-label for="totp">{{ "enableAutoTotpCopy" | i18n }}</bit-label>
id="totp" </bit-form-control>
type="checkbox" <bit-form-field>
(change)="updateAutoTotpCopy()" <bit-label for="clearClipboard">{{ "clearClipboard" | i18n }}</bit-label>
[(ngModel)]="enableAutoTotpCopy" <bit-select
/> formControlName="clearClipboard"
<bit-label for="totp">{{ "enableAutoTotpCopy" | i18n }}</bit-label> aria-describedby="clearClipboardHelp"
</bit-form-control> bitInput
<bit-form-field> id="clearClipboard"
<bit-label for="clearClipboard">{{ "clearClipboard" | i18n }}</bit-label> >
<select <bit-option
aria-describedby="clearClipboardHelp" *ngFor="let option of clearClipboardOptions"
bitInput [label]="option.name"
id="clearClipboard" [value]="option.value"
(change)="saveClearClipboard()" ></bit-option>
[(ngModel)]="clearClipboard" </bit-select>
> <bit-hint class="tw-text-sm" id="clearClipboardHelp">
<option {{ "clearClipboardDesc" | i18n }}
*ngFor="let o of clearClipboardOptions" </bit-hint>
[label]="o.name" </bit-form-field>
[ngValue]="o.value" <bit-form-field disableMargin>
></option> <bit-label for="defaultUriMatch">{{ "defaultUriMatchDetection" | i18n }}</bit-label>
</select> <bit-select
<bit-hint class="tw-text-sm" id="clearClipboardHelp"> formControlName="defaultUriMatch"
{{ "clearClipboardDesc" | i18n }} aria-describedby="defaultUriMatchHelp"
</bit-hint> bitInput
</bit-form-field> id="defaultUriMatch"
<bit-form-field disableMargin> >
<bit-label for="defaultUriMatch">{{ "defaultUriMatchDetection" | i18n }}</bit-label> <bit-option
<select *ngFor="let option of uriMatchOptions"
aria-describedby="defaultUriMatchHelp" [label]="option.name"
bitInput [value]="option.value"
id="defaultUriMatch" ></bit-option>
(change)="saveDefaultUriMatch()" </bit-select>
[(ngModel)]="defaultUriMatch" <bit-hint class="tw-text-sm" id="defaultUriMatchHelp">
> {{ "defaultUriMatchDetectionDesc" | i18n }}
<option *ngFor="let o of uriMatchOptions" [label]="o.name" [ngValue]="o.value"></option> </bit-hint>
</select> </bit-form-field>
<bit-hint class="tw-text-sm" id="defaultUriMatchHelp"> </bit-card>
{{ "defaultUriMatchDetectionDesc" | i18n }} </form>
</bit-hint>
</bit-form-field>
</bit-card>
</bit-section> </bit-section>
<bit-section *ngIf="blockBrowserInjectionsByDomainEnabled" disableMargin> <bit-section *ngIf="blockBrowserInjectionsByDomainEnabled" disableMargin>
<bit-item> <bit-item>

View File

@@ -1,8 +1,15 @@
// FIXME: Update this file to be type safe and remove this and next line // FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore // @ts-strict-ignore
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core"; import { Component, DestroyRef, OnInit } from "@angular/core";
import { FormsModule } from "@angular/forms"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import {
FormsModule,
ReactiveFormsModule,
FormBuilder,
FormGroup,
FormControl,
} from "@angular/forms";
import { RouterModule } from "@angular/router"; import { RouterModule } from "@angular/router";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
@@ -73,6 +80,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
SectionHeaderComponent, SectionHeaderComponent,
SelectModule, SelectModule,
TypographyModule, TypographyModule,
ReactiveFormsModule,
], ],
}) })
export class AutofillComponent implements OnInit { export class AutofillComponent implements OnInit {
@@ -94,6 +102,18 @@ export class AutofillComponent implements OnInit {
protected autofillOnPageLoadFromPolicy$ = protected autofillOnPageLoadFromPolicy$ =
this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$; this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$;
protected autofillOnPageLoadForm = new FormGroup({
autofillOnPageLoad: new FormControl(),
defaultAutofill: new FormControl(),
});
protected additionalOptionsForm = new FormGroup({
enableContextMenuItem: new FormControl(),
enableAutoTotpCopy: new FormControl(),
clearClipboard: new FormControl(),
defaultUriMatch: new FormControl(),
});
enableAutofillOnPageLoad: boolean = false; enableAutofillOnPageLoad: boolean = false;
enableInlineMenu: boolean = false; enableInlineMenu: boolean = false;
enableInlineMenuOnIconSelect: boolean = false; enableInlineMenuOnIconSelect: boolean = false;
@@ -121,10 +141,12 @@ export class AutofillComponent implements OnInit {
private messagingService: MessagingService, private messagingService: MessagingService,
private vaultSettingsService: VaultSettingsService, private vaultSettingsService: VaultSettingsService,
private configService: ConfigService, private configService: ConfigService,
private formBuilder: FormBuilder,
private destroyRef: DestroyRef,
) { ) {
this.autofillOnPageLoadOptions = [ this.autofillOnPageLoadOptions = [
{ name: i18nService.t("autoFillOnPageLoadYes"), value: true }, { name: this.i18nService.t("autoFillOnPageLoadYes"), value: true },
{ name: i18nService.t("autoFillOnPageLoadNo"), value: false }, { name: this.i18nService.t("autoFillOnPageLoadNo"), value: false },
]; ];
this.clearClipboardOptions = [ this.clearClipboardOptions = [
{ name: i18nService.t("never"), value: ClearClipboardDelay.Never }, { name: i18nService.t("never"), value: ClearClipboardDelay.Never },
@@ -181,27 +203,106 @@ export class AutofillComponent implements OnInit {
this.inlineMenuVisibility === AutofillOverlayVisibility.OnFieldFocus || this.inlineMenuVisibility === AutofillOverlayVisibility.OnFieldFocus ||
this.enableInlineMenuOnIconSelect; this.enableInlineMenuOnIconSelect;
this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
value
? this.autofillOnPageLoadForm.controls.autofillOnPageLoad.disable({ emitEvent: false })
: this.autofillOnPageLoadForm.controls.autofillOnPageLoad.enable({ emitEvent: false });
});
this.enableAutofillOnPageLoad = await firstValueFrom( this.enableAutofillOnPageLoad = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoad$, this.autofillSettingsService.autofillOnPageLoad$,
); );
this.autofillOnPageLoadForm.controls.autofillOnPageLoad.patchValue(
this.enableAutofillOnPageLoad,
{ emitEvent: false },
);
this.autofillOnPageLoadDefault = await firstValueFrom( this.autofillOnPageLoadDefault = await firstValueFrom(
this.autofillSettingsService.autofillOnPageLoadDefault$, this.autofillSettingsService.autofillOnPageLoadDefault$,
); );
if (this.enableAutofillOnPageLoad === false) {
this.autofillOnPageLoadForm.controls.defaultAutofill.disable();
}
this.autofillOnPageLoadForm.controls.defaultAutofill.patchValue(
this.autofillOnPageLoadDefault,
{ emitEvent: false },
);
this.autofillOnPageLoadForm.controls.autofillOnPageLoad.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
void this.autofillSettingsService.setAutofillOnPageLoad(value);
this.enableDefaultAutofillControl(value);
});
this.autofillOnPageLoadForm.controls.defaultAutofill.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
void this.autofillSettingsService.setAutofillOnPageLoadDefault(value);
});
/** Additional options form */
this.enableContextMenuItem = await firstValueFrom( this.enableContextMenuItem = await firstValueFrom(
this.autofillSettingsService.enableContextMenu$, this.autofillSettingsService.enableContextMenu$,
); );
this.additionalOptionsForm.controls.enableContextMenuItem.patchValue(
this.enableContextMenuItem,
{ emitEvent: false },
);
this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$); this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$);
this.additionalOptionsForm.controls.enableAutoTotpCopy.patchValue(this.enableAutoTotpCopy, {
emitEvent: false,
});
this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$); this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$);
this.additionalOptionsForm.controls.clearClipboard.patchValue(this.clearClipboard, {
emitEvent: false,
});
const defaultUriMatch = await firstValueFrom( const defaultUriMatch = await firstValueFrom(
this.domainSettingsService.defaultUriMatchStrategy$, this.domainSettingsService.defaultUriMatchStrategy$,
); );
this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch; this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch;
this.additionalOptionsForm.controls.defaultUriMatch.patchValue(this.defaultUriMatch, {
emitEvent: false,
});
this.additionalOptionsForm.controls.enableContextMenuItem.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
void this.autofillSettingsService.setEnableContextMenu(value);
this.messagingService.send("bgUpdateContextMenu");
});
this.additionalOptionsForm.controls.enableAutoTotpCopy.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
void this.autofillSettingsService.setAutoCopyTotp(value);
});
this.additionalOptionsForm.controls.clearClipboard.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
void this.autofillSettingsService.setClearClipboardDelay(value);
});
this.additionalOptionsForm.controls.defaultUriMatch.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
void this.domainSettingsService.setDefaultUriMatchStrategy(value);
});
const command = await this.platformUtilsService.getAutofillKeyboardShortcut(); const command = await this.platformUtilsService.getAutofillKeyboardShortcut();
await this.setAutofillKeyboardHelperText(command); await this.setAutofillKeyboardHelperText(command);
@@ -230,17 +331,16 @@ export class AutofillComponent implements OnInit {
await this.requestPrivacyPermission(); await this.requestPrivacyPermission();
} }
} }
async getAutofillOnPageLoadFromPolicy() {
async updateAutofillOnPageLoad() { await firstValueFrom(this.autofillOnPageLoadFromPolicy$);
await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutofillOnPageLoad);
} }
async updateAutofillOnPageLoadDefault() { enableDefaultAutofillControl(enable: boolean = true) {
await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autofillOnPageLoadDefault); if (enable) {
} this.autofillOnPageLoadForm.controls.defaultAutofill.enable();
} else {
async saveDefaultUriMatch() { this.autofillOnPageLoadForm.controls.defaultAutofill.disable();
await this.domainSettingsService.setDefaultUriMatchStrategy(this.defaultUriMatch); }
} }
private async setAutofillKeyboardHelperText(command: string) { private async setAutofillKeyboardHelperText(command: string) {
@@ -388,19 +488,6 @@ export class AutofillComponent implements OnInit {
return await BrowserApi.permissionsGranted(["privacy"]); return await BrowserApi.permissionsGranted(["privacy"]);
} }
async updateContextMenuItem() {
await this.autofillSettingsService.setEnableContextMenu(this.enableContextMenuItem);
this.messagingService.send("bgUpdateContextMenu");
}
async updateAutoTotpCopy() {
await this.autofillSettingsService.setAutoCopyTotp(this.enableAutoTotpCopy);
}
async saveClearClipboard() {
await this.autofillSettingsService.setClearClipboardDelay(this.clearClipboard);
}
async updateShowCardsCurrentTab() { async updateShowCardsCurrentTab() {
await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab); await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab);
} }