1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-17598] Long custom fields (#13668)

* add custom directive to use the angular CDK resize textarea directive

* swap to textarea to allow for full content to be shown when view text or hidden custom fields

* add text-field styling to web sass file

* move angular import to CL scss file

* add `textarea` to selector to enforce directive usage only on textareas
This commit is contained in:
Nick Krantz
2025-04-02 11:58:31 -05:00
committed by GitHub
parent 9b3c28fcea
commit 3c83165b4e
6 changed files with 99 additions and 4 deletions

View File

@@ -4,6 +4,7 @@
@import "../../../../libs/angular/src/scss/icons.scss";
@import "../../../../libs/components/src/multi-select/scss/bw.theme";
@import "@angular/cdk/overlay-prebuilt.css";
@import "@angular/cdk/text-field-prebuilt.css";
//@import "~bootstrap/scss/bootstrap";
@import "~bootstrap/scss/_functions";

View File

@@ -1,4 +1,5 @@
@import "@angular/cdk/a11y-prebuilt.css";
@import "@angular/cdk/text-field-prebuilt.css";
@import "./reset.css";
@import "./popover/popover.component.css";
@import "./toast/toast.tokens.css";

View File

@@ -5,13 +5,19 @@
<bit-card>
<div
class="tw-border-secondary-300 [&_bit-form-field:last-of-type]:tw-mb-0"
*ngFor="let field of cipher.fields; let last = last"
*ngFor="let field of cipher.fields; let last = last; let i = index"
[ngClass]="{ 'tw-mb-4': !last }"
data-testid="custom-field"
>
<bit-form-field *ngIf="field.type === fieldType.Text" [disableReadOnlyBorder]="last">
<bit-label [appTextDrag]="field.value">{{ field.name }}</bit-label>
<input readonly bitInput type="text" [value]="field.value" aria-readonly="true" />
<textarea
readonly
bitInput
[value]="field.value"
aria-readonly="true"
vaultAutosizeReadOnlyTextArea
></textarea>
<button
bitIconButton="bwi-clone"
bitSuffix
@@ -32,14 +38,26 @@
[value]="field.value"
aria-readonly="true"
class="tw-font-mono"
*ngIf="!revealedHiddenFields.includes(i)"
/>
<!-- `type="password"` is only available for inputs, but textarea should show the entire field when visible. -->
<textarea
*ngIf="revealedHiddenFields.includes(i)"
readonly
bitInput
type="password"
[value]="field.value"
aria-readonly="true"
class="tw-font-mono"
vaultAutosizeReadOnlyTextArea
></textarea>
<button
bitSuffix
type="button"
bitIconButton
bitPasswordInputToggle
*ngIf="canViewPassword"
(toggledChange)="logHiddenEvent($event)"
(toggledChange)="toggleHiddenField($event, i)"
></button>
<button
bitIconButton="bwi-clone"

View File

@@ -23,6 +23,8 @@ import {
CheckboxModule,
} from "@bitwarden/components";
import { VaultAutosizeReadOnlyTextArea } from "../../directives/readonly-textarea.directive";
@Component({
selector: "app-custom-fields-v2",
templateUrl: "custom-fields-v2.component.html",
@@ -38,6 +40,7 @@ import {
SectionHeaderComponent,
TypographyModule,
CheckboxModule,
VaultAutosizeReadOnlyTextArea,
],
})
export class CustomFieldV2Component implements OnInit {
@@ -45,6 +48,9 @@ export class CustomFieldV2Component implements OnInit {
fieldType = FieldType;
fieldOptions: any;
/** Indexes of hidden fields that are revealed */
revealedHiddenFields: number[] = [];
constructor(
private i18nService: I18nService,
private eventCollectionService: EventCollectionService,
@@ -63,7 +69,13 @@ export class CustomFieldV2Component implements OnInit {
return this.cipher.viewPassword;
}
async logHiddenEvent(hiddenFieldVisible: boolean) {
async toggleHiddenField(hiddenFieldVisible: boolean, index: number) {
if (hiddenFieldVisible) {
this.revealedHiddenFields.push(index);
} else {
this.revealedHiddenFields = this.revealedHiddenFields.filter((i) => i !== index);
}
if (hiddenFieldVisible) {
await this.eventCollectionService.collect(
EventType.Cipher_ClientToggledHiddenFieldVisible,

View File

@@ -0,0 +1,34 @@
import { CdkTextareaAutosize } from "@angular/cdk/text-field";
import { EventEmitter, NgZone } from "@angular/core";
import { VaultAutosizeReadOnlyTextArea } from "./readonly-textarea.directive";
describe("VaultAutosizeReadOnlyTextArea", () => {
let directive: VaultAutosizeReadOnlyTextArea;
let onStable: EventEmitter<void>;
beforeEach(async () => {
onStable = new EventEmitter<void>();
directive = new VaultAutosizeReadOnlyTextArea(
{ enabled: undefined } as unknown as CdkTextareaAutosize,
{ onStable } as NgZone,
);
});
it("disables CdkTextareaAutosize by default", () => {
expect(directive["autosize"].enabled).toBe(false);
});
it("enables CdkTextareaAutosize after view init", async () => {
expect(directive["autosize"].enabled).toBe(false);
const viewInitPromise = directive.ngAfterViewInit();
onStable.emit();
await viewInitPromise;
expect(directive["autosize"].enabled).toBe(true);
});
});

View File

@@ -0,0 +1,29 @@
import { CdkTextareaAutosize, TextFieldModule } from "@angular/cdk/text-field";
import { AfterViewInit, Directive, Host, NgZone } from "@angular/core";
import { firstValueFrom } from "rxjs";
@Directive({
standalone: true,
selector: "textarea[vaultAutosizeReadOnlyTextArea]",
providers: [TextFieldModule],
hostDirectives: [CdkTextareaAutosize],
})
export class VaultAutosizeReadOnlyTextArea implements AfterViewInit {
constructor(
@Host() private autosize: CdkTextareaAutosize,
private ngZone: NgZone,
) {
// initially disable autosize
this.autosize.enabled = false;
}
// <textarea>s are commonly used within `bit-form-field` components which render
// content within templates. This causes the `CdkTextareaAutosize` to error out
// when trying to access the `parentNode` of the textarea. To avoid this, wait
// for the next change detection cycle to enable the autosize directive.
async ngAfterViewInit(): Promise<void> {
await firstValueFrom(this.ngZone.onStable);
this.autosize.enabled = true;
}
}