mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-28 12:45:45 +01:00
[PM-2102] Implement logic to keep row control enable/disable status in sync with the access item properties whenever the parent control is enabled/disabled (#5433)
Angular 15 introduced a breaking change that calls setDisabledState() whenever a CVA is added. This was re-enabling all the internal form group rows (even those that should have remained disabled).
This commit is contained in:
parent
3da7fc7cb3
commit
3f7a63b2c6
@ -1,7 +1,14 @@
|
|||||||
import { Component, forwardRef, Input, OnDestroy, OnInit } from "@angular/core";
|
import { Component, forwardRef, Input, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from "@angular/forms";
|
import {
|
||||||
|
ControlValueAccessor,
|
||||||
|
FormBuilder,
|
||||||
|
FormControl,
|
||||||
|
FormGroup,
|
||||||
|
NG_VALUE_ACCESSOR,
|
||||||
|
} from "@angular/forms";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
|
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
|
||||||
import { FormSelectionList } from "@bitwarden/angular/utils/form-selection-list";
|
import { FormSelectionList } from "@bitwarden/angular/utils/form-selection-list";
|
||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view";
|
import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view";
|
||||||
@ -47,6 +54,40 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On
|
|||||||
private notifyOnTouch: () => void;
|
private notifyOnTouch: () => void;
|
||||||
private pauseChangeNotification: boolean;
|
private pauseChangeNotification: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the enabled/disabled state of provided row form group based on the item's readonly state.
|
||||||
|
* If a row is enabled, it also updates the enabled/disabled state of the permission control
|
||||||
|
* based on the item's accessAllItems state and the current value of `permissionMode`.
|
||||||
|
* @param controlRow - The form group for the row to update
|
||||||
|
* @param item - The access item that is represented by the row
|
||||||
|
*/
|
||||||
|
private updateRowControlDisableState = (
|
||||||
|
controlRow: FormGroup<ControlsOf<AccessItemValue>>,
|
||||||
|
item: AccessItemView
|
||||||
|
) => {
|
||||||
|
// Disable entire row form group if readonly
|
||||||
|
if (item.readonly) {
|
||||||
|
controlRow.disable();
|
||||||
|
} else {
|
||||||
|
controlRow.enable();
|
||||||
|
|
||||||
|
// The enable() above also enables the permission control, so we need to disable it again
|
||||||
|
// Disable permission control if accessAllItems is enabled or not in Edit mode
|
||||||
|
if (item.accessAllItems || this.permissionMode != PermissionMode.Edit) {
|
||||||
|
controlRow.controls.permission.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the enabled/disabled state of ALL row form groups based on each item's readonly state.
|
||||||
|
*/
|
||||||
|
private updateAllRowControlDisableStates = () => {
|
||||||
|
this.selectionList.forEachControlItem((controlRow, item) => {
|
||||||
|
this.updateRowControlDisableState(controlRow as FormGroup<ControlsOf<AccessItemValue>>, item);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The internal selection list that tracks the value of this form control / component.
|
* The internal selection list that tracks the value of this form control / component.
|
||||||
* It's responsible for keeping items sorted and synced with the rendered form controls
|
* It's responsible for keeping items sorted and synced with the rendered form controls
|
||||||
@ -55,21 +96,13 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On
|
|||||||
protected selectionList = new FormSelectionList<AccessItemView, AccessItemValue>((item) => {
|
protected selectionList = new FormSelectionList<AccessItemView, AccessItemValue>((item) => {
|
||||||
const permissionControl = this.formBuilder.control(this.initialPermission);
|
const permissionControl = this.formBuilder.control(this.initialPermission);
|
||||||
|
|
||||||
const fg = this.formBuilder.group({
|
const fg = this.formBuilder.group<ControlsOf<AccessItemValue>>({
|
||||||
id: item.id,
|
id: new FormControl(item.id),
|
||||||
type: item.type,
|
type: new FormControl(item.type),
|
||||||
permission: permissionControl,
|
permission: permissionControl,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Disable entire row form group if readonly
|
this.updateRowControlDisableState(fg, item);
|
||||||
if (item.readonly) {
|
|
||||||
fg.disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable permission control if accessAllItems is enabled
|
|
||||||
if (item.accessAllItems || this.permissionMode != PermissionMode.Edit) {
|
|
||||||
permissionControl.disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return fg;
|
return fg;
|
||||||
}, this._itemComparator.bind(this));
|
}, this._itemComparator.bind(this));
|
||||||
@ -124,14 +157,8 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On
|
|||||||
|
|
||||||
set permissionMode(value: PermissionMode) {
|
set permissionMode(value: PermissionMode) {
|
||||||
this._permissionMode = value;
|
this._permissionMode = value;
|
||||||
// Toggle any internal permission controls
|
// Update any internal permission controls
|
||||||
for (const control of this.selectionList.formArray.controls) {
|
this.updateAllRowControlDisableStates();
|
||||||
if (value == PermissionMode.Edit) {
|
|
||||||
control.get("permission").enable();
|
|
||||||
} else {
|
|
||||||
control.get("permission").disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private _permissionMode: PermissionMode = PermissionMode.Hidden;
|
private _permissionMode: PermissionMode = PermissionMode.Hidden;
|
||||||
|
|
||||||
@ -189,6 +216,10 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On
|
|||||||
this.formGroup.disable();
|
this.formGroup.disable();
|
||||||
} else {
|
} else {
|
||||||
this.formGroup.enable();
|
this.formGroup.enable();
|
||||||
|
|
||||||
|
// The enable() above automatically enables all the row controls,
|
||||||
|
// so we need to disable the readonly ones again
|
||||||
|
this.updateAllRowControlDisableStates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,4 +198,18 @@ export class FormSelectionList<
|
|||||||
this.selectItem(selectedItem.id, selectedItem);
|
this.selectItem(selectedItem.id, selectedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to iterate over each "selected" form control and its corresponding item
|
||||||
|
* @param fn - The function to call for each form control and its corresponding item
|
||||||
|
*/
|
||||||
|
forEachControlItem(
|
||||||
|
fn: (control: AbstractControl<Partial<TControlValue>, TControlValue>, value: TItem) => void
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < this.formArray.length; i++) {
|
||||||
|
// The selectedItems array and formArray are explicitly kept in sync,
|
||||||
|
// so we can safely assume the index of the form control and item are the same
|
||||||
|
fn(this.formArray.at(i), this.selectedItems[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user