diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts
index 52d70e8652a..50577472120 100644
--- a/libs/vault/src/cipher-form/cipher-form.stories.ts
+++ b/libs/vault/src/cipher-form/cipher-form.stories.ts
@@ -1,6 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { importProvidersFrom, signal } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
import { action } from "@storybook/addon-actions";
import {
applicationConfig,
@@ -225,6 +226,14 @@ export default {
getFeatureFlag: () => Promise.resolve(false),
},
},
+ {
+ provide: ActivatedRoute,
+ useValue: {
+ snapshot: {
+ queryParams: {},
+ },
+ },
+ },
],
}),
componentWrapperDecorator(
diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts
index 58a9b8f3965..af39ea96c16 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts
+++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts
@@ -83,4 +83,24 @@ describe("AddEditCustomFieldDialogComponent", () => {
expect.objectContaining({ value: FieldType.Linked }),
);
});
+
+ it("does not filter out 'Hidden' field type when 'disallowHiddenField' is false", () => {
+ dialogData.disallowHiddenField = false;
+ fixture = TestBed.createComponent(AddEditCustomFieldDialogComponent);
+ component = fixture.componentInstance;
+
+ expect(component.fieldTypeOptions).toContainEqual(
+ expect.objectContaining({ value: FieldType.Hidden }),
+ );
+ });
+
+ it("filers out 'Hidden' field type when 'disallowHiddenField' is true", () => {
+ dialogData.disallowHiddenField = true;
+ fixture = TestBed.createComponent(AddEditCustomFieldDialogComponent);
+ component = fixture.componentInstance;
+
+ expect(component.fieldTypeOptions).not.toContainEqual(
+ expect.objectContaining({ value: FieldType.Hidden }),
+ );
+ });
});
diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts
index bdf5345672d..72bdf5dca1a 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts
+++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts
@@ -25,6 +25,7 @@ export type AddEditCustomFieldDialogData = {
cipherType: CipherType;
/** When provided, dialog will display edit label variants */
editLabelConfig?: { index: number; label: string };
+ disallowHiddenField?: boolean;
};
@Component({
@@ -68,6 +69,9 @@ export class AddEditCustomFieldDialogComponent {
this.variant = data.editLabelConfig ? "edit" : "add";
this.fieldTypeOptions = this.fieldTypeOptions.filter((option) => {
+ if (this.data.disallowHiddenField && option.value === FieldType.Hidden) {
+ return false;
+ }
// Filter out the Linked field type for Secure Notes
if (this.data.cipherType === CipherType.SecureNote) {
return option.value !== FieldType.Linked;
diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html
index 3bce3c5f385..1305bcdae05 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html
+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html
@@ -89,7 +89,7 @@
bitIconButton="bwi-pencil-square"
class="tw-self-center tw-mt-2"
data-testid="edit-custom-field-button"
- *ngIf="!isPartialEdit"
+ *ngIf="canEdit(field.value.type)"
>
diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts
index fb9664594ed..ced8763f895 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts
+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts
@@ -45,7 +45,9 @@ describe("CustomFieldsComponent", () => {
announce = jest.fn().mockResolvedValue(null);
patchCipher = jest.fn();
originalCipherView = new CipherView();
- config = {} as CipherFormConfig;
+ config = {
+ collections: [],
+ } as CipherFormConfig;
await TestBed.configureTestingModule({
imports: [CustomFieldsComponent],
@@ -463,5 +465,91 @@ describe("CustomFieldsComponent", () => {
// "reorder boolean label to position 4 of 4"
expect(announce).toHaveBeenCalledWith("reorderFieldDown boolean label 4 4", "assertive");
});
+
+ it("hides reorder buttons when in partial edit mode", () => {
+ originalCipherView.fields = mockFieldViews;
+ config.mode = "partial-edit";
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ toggleItems = fixture.debugElement.queryAll(
+ By.css('button[data-testid="reorder-toggle-button"]'),
+ );
+
+ expect(toggleItems).toHaveLength(0);
+ });
+ });
+
+ it("shows all reorders button when in edit mode and viewPassword is true", () => {
+ originalCipherView.fields = mockFieldViews;
+ originalCipherView.viewPassword = true;
+ config.mode = "edit";
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ const toggleItems = fixture.debugElement.queryAll(
+ By.css('button[data-testid="reorder-toggle-button"]'),
+ );
+ expect(toggleItems).toHaveLength(4);
+ });
+
+ it("shows all reorder buttons except for hidden fields when in edit mode and viewPassword is false", () => {
+ originalCipherView.fields = mockFieldViews;
+ originalCipherView.viewPassword = false;
+ config.mode = "edit";
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ const toggleItems = fixture.debugElement.queryAll(
+ By.css('button[data-testid="reorder-toggle-button"]'),
+ );
+
+ expect(toggleItems).toHaveLength(3);
+ });
+
+ describe("edit button", () => {
+ it("hides the edit button when in partial-edit mode", () => {
+ originalCipherView.fields = mockFieldViews;
+ config.mode = "partial-edit";
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ const editButtons = fixture.debugElement.queryAll(
+ By.css('button[data-testid="edit-custom-field-button"]'),
+ );
+ expect(editButtons).toHaveLength(0);
+ });
+
+ it("shows all the edit buttons when in edit mode and viewPassword is true", () => {
+ originalCipherView.fields = mockFieldViews;
+ originalCipherView.viewPassword = true;
+ config.mode = "edit";
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ const editButtons = fixture.debugElement.queryAll(
+ By.css('button[data-testid="edit-custom-field-button"]'),
+ );
+ expect(editButtons).toHaveLength(4);
+ });
+
+ it("shows all the edit buttons except for hidden fields when in edit mode and viewPassword is false", () => {
+ originalCipherView.fields = mockFieldViews;
+ originalCipherView.viewPassword = false;
+ config.mode = "edit";
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ const editButtons = fixture.debugElement.queryAll(
+ By.css('button[data-testid="edit-custom-field-button"]'),
+ );
+ expect(editButtons).toHaveLength(3);
+ });
});
});
diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
index dd3fd8c24a8..49e9e109b74 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
@@ -116,6 +116,8 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
/** Emits when a new custom field should be focused */
private focusOnNewInput$ = new Subject();
+ disallowHiddenField?: boolean;
+
destroyed$: DestroyRef;
FieldType = FieldType;
@@ -141,6 +143,13 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
return this.customFieldsForm.controls.fields as FormArray;
}
+ canEdit(type: FieldType): boolean {
+ return (
+ !this.isPartialEdit &&
+ (type !== FieldType.Hidden || this.cipherFormContainer.originalCipherView?.viewPassword)
+ );
+ }
+
ngOnInit() {
const linkedFieldsOptionsForCipher = this.getLinkedFieldsOptionsForCipher();
const optionsArray = Array.from(linkedFieldsOptionsForCipher?.entries() ?? []);
@@ -210,6 +219,7 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
/** Opens the add/edit custom field dialog */
openAddEditCustomFieldDialog(editLabelConfig?: AddEditCustomFieldDialogData["editLabelConfig"]) {
+ const { cipherType, mode, originalCipher } = this.cipherFormContainer.config;
this.dialogRef = this.dialogService.open(
AddEditCustomFieldDialogComponent,
{
@@ -217,8 +227,9 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
addField: this.addField.bind(this),
updateLabel: this.updateLabel.bind(this),
removeField: this.removeField.bind(this),
- cipherType: this.cipherFormContainer.config.cipherType,
+ cipherType,
editLabelConfig,
+ disallowHiddenField: mode === "edit" && !originalCipher.viewPassword,
},
},
);