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:
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
29
libs/vault/src/directives/readonly-textarea.directive.ts
Normal file
29
libs/vault/src/directives/readonly-textarea.directive.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user