[ACS-10294] KN: Personal Files: On activating the button, focus does not move into newly opened section (#5014)

This commit is contained in:
Dominik Iwanek
2026-01-28 10:08:08 +01:00
committed by GitHub
parent 22aeec5c8d
commit ebca95d981
3 changed files with 92 additions and 12 deletions
@@ -9,6 +9,7 @@
</div>
<fieldset
#conditionRow
[attr.aria-labelledby]="'conditions-group-label-' + (childCondition ? 'nested' : 'main')"
class="aca-rule-composite-condition__form__row"
*ngFor="let control of conditionFormControls; let i = index">
@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { RuleCompositeConditionUiComponent } from './rule-composite-condition.ui-component';
import { NoopTranslateModule, UnitTestingUtils } from '@alfresco/adf-core';
import { DebugElement } from '@angular/core';
@@ -33,6 +33,7 @@ import {
} from '../../mock/conditions.mock';
import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-content-services';
import { FocusTrapFactory } from '@angular/cdk/a11y';
describe('RuleCompositeConditionUiComponent', () => {
let fixture: ComponentFixture<RuleCompositeConditionUiComponent>;
@@ -166,4 +167,36 @@ describe('RuleCompositeConditionUiComponent', () => {
expect(unitTestingUtils.getAllByCSS('[conditions-group-label-nested]')).toBeTruthy();
});
});
describe('Focus management', () => {
it('should focus the newly added simple condition', fakeAsync(() => {
fixture.detectChanges();
const focusTrapFactory = TestBed.inject(FocusTrapFactory);
const focusTrapSpy = jasmine.createSpyObj('FocusTrap', ['focusFirstTabbableElement', 'destroy']);
spyOn(focusTrapFactory, 'create').and.returnValue(focusTrapSpy);
unitTestingUtils.clickByDataAutomationId('add-condition-button');
fixture.detectChanges();
tick();
expect(focusTrapFactory.create).toHaveBeenCalled();
expect(focusTrapSpy.focusFirstTabbableElement).toHaveBeenCalled();
expect(focusTrapSpy.destroy).toHaveBeenCalled();
}));
it('should focus the newly added composite condition', fakeAsync(() => {
fixture.detectChanges();
const focusTrapFactory = TestBed.inject(FocusTrapFactory);
const focusTrapSpy = jasmine.createSpyObj('FocusTrap', ['focusFirstTabbableElement', 'destroy']);
spyOn(focusTrapFactory, 'create').and.returnValue(focusTrapSpy);
unitTestingUtils.clickByDataAutomationId('add-group-button');
fixture.detectChanges();
tick();
expect(focusTrapFactory.create).toHaveBeenCalled();
expect(focusTrapSpy.focusFirstTabbableElement).toHaveBeenCalled();
expect(focusTrapSpy.destroy).toHaveBeenCalled();
}));
});
});
@@ -22,7 +22,22 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, forwardRef, HostBinding, Input, OnChanges, OnDestroy, SimpleChanges, ViewEncapsulation } from '@angular/core';
import {
AfterViewInit,
Component,
DestroyRef,
ElementRef,
forwardRef,
HostBinding,
inject,
Input,
OnChanges,
QueryList,
SimpleChanges,
ViewChildren,
ViewEncapsulation
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { RuleCompositeCondition } from '../../model/rule-composite-condition.model';
import { ControlValueAccessor, FormArray, FormControl, FormGroup, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
@@ -34,6 +49,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatIconModule } from '@angular/material/icon';
import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
import { FocusTrapFactory } from '@angular/cdk/a11y';
@Component({
imports: [
@@ -60,16 +76,21 @@ import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-compo
}
]
})
export class RuleCompositeConditionUiComponent implements ControlValueAccessor, OnDestroy, OnChanges {
export class RuleCompositeConditionUiComponent implements ControlValueAccessor, OnChanges, AfterViewInit {
@HostBinding('class.aca-secondaryBackground')
@Input()
secondaryBackground = false;
@HostBinding('class.aca-childCompositeCondition')
@Input()
childCondition = false;
@Input()
readOnly = false;
@ViewChildren('conditionRow', { read: ElementRef })
private readonly conditionRows: QueryList<ElementRef<HTMLElement>>;
readonly isOrImplemented = false;
form = new FormGroup({
@@ -79,23 +100,31 @@ export class RuleCompositeConditionUiComponent implements ControlValueAccessor,
simpleConditions: new FormArray([])
});
private formSubscription = this.form.valueChanges.subscribe((value: any) => {
this.onChange(value);
this.onTouch();
});
public invertedControl = this.form.get('inverted') as FormControl;
public booleanModeControl = this.form.get('booleanMode') as FormControl;
private readonly destroyRef = inject(DestroyRef);
private readonly focusTrapFactory = inject(FocusTrapFactory);
constructor() {
this.form.valueChanges.pipe(takeUntilDestroyed()).subscribe((value: RuleCompositeCondition) => {
this.onChange(value);
this.onTouch();
});
}
get compositeConditionsFormArray(): FormArray {
return this.form.get('compositeConditions') as FormArray;
}
get simpleConditionsFormArray(): FormArray {
return this.form.get('simpleConditions') as FormArray;
}
get conditionFormControls(): FormControl[] {
return [...(this.compositeConditionsFormArray.controls as FormControl[]), ...(this.simpleConditionsFormArray.controls as FormControl[])];
}
get hasNoConditions(): boolean {
return this.conditionFormControls.length === 0;
}
@@ -103,6 +132,12 @@ export class RuleCompositeConditionUiComponent implements ControlValueAccessor,
onChange: (condition: RuleCompositeCondition) => void = () => undefined;
onTouch: () => void = () => undefined;
ngAfterViewInit(): void {
this.conditionRows.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.focusLastCondition();
});
}
writeValue(value: RuleCompositeCondition) {
this.form.get('inverted').setValue(value.inverted);
this.form.get('booleanMode').setValue(value.booleanMode);
@@ -148,10 +183,6 @@ export class RuleCompositeConditionUiComponent implements ControlValueAccessor,
this.compositeConditionsFormArray.push(new FormControl(newCondition));
}
ngOnDestroy() {
this.formSubscription.unsubscribe();
}
ngOnChanges(changes: SimpleChanges) {
const readOnly = changes['readOnly']?.currentValue;
@@ -165,4 +196,19 @@ export class RuleCompositeConditionUiComponent implements ControlValueAccessor,
}
}
}
private focusLastCondition(): void {
const rows = this.conditionRows.toArray();
const lastRow = rows[rows.length - 1]?.nativeElement;
if (!lastRow) {
return;
}
setTimeout(() => {
const focusTrap = this.focusTrapFactory.create(lastRow);
focusTrap.focusFirstTabbableElement();
focusTrap.destroy();
});
}
}