mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-11317] a11y Fix: Tags interactive controls are not manageable via keyboard (#11845)
* [ACS-11317] Fix a11y: Delete button in create tags popup is not focusable via keyboard * [ACS-11317] use UnitTestingUtils * [ACS-11317] cr fixes
This commit is contained in:
@@ -82,8 +82,7 @@ adf-tags-creator {
|
|||||||
/* stylelint-disable selector-class-pattern */
|
/* stylelint-disable selector-class-pattern */
|
||||||
.mdc-evolution-chip-set .mat-mdc-standard-chip {
|
.mdc-evolution-chip-set .mat-mdc-standard-chip {
|
||||||
.mdc-evolution-chip__cell--primary,
|
.mdc-evolution-chip__cell--primary,
|
||||||
.mdc-evolution-chip__action--primary,
|
.mdc-evolution-chip__action--primary {
|
||||||
.mat-mdc-chip-action-label {
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { TagsCreatorMode, TagService } from '@alfresco/adf-content-services';
|
import { TagsCreatorMode, TagService } from '@alfresco/adf-content-services';
|
||||||
import { NotificationService } from '@alfresco/adf-core';
|
import { NotificationService, UnitTestingUtils } from '@alfresco/adf-core';
|
||||||
import { HarnessLoader } from '@angular/cdk/testing';
|
import { HarnessLoader } from '@angular/cdk/testing';
|
||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
@@ -24,7 +24,6 @@ import { ComponentFixture, discardPeriodicTasks, fakeAsync, flush, TestBed, tick
|
|||||||
import { MatChipHarness } from '@angular/material/chips/testing';
|
import { MatChipHarness } from '@angular/material/chips/testing';
|
||||||
import { MatError } from '@angular/material/form-field';
|
import { MatError } from '@angular/material/form-field';
|
||||||
import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
|
import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { EMPTY, of, throwError } from 'rxjs';
|
import { EMPTY, of, throwError } from 'rxjs';
|
||||||
import { TagsCreatorComponent } from './tags-creator.component';
|
import { TagsCreatorComponent } from './tags-creator.component';
|
||||||
|
|
||||||
@@ -34,6 +33,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
let tagService: TagService;
|
let tagService: TagService;
|
||||||
let notificationService: NotificationService;
|
let notificationService: NotificationService;
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
|
let testingUtils: UnitTestingUtils;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -65,6 +65,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
tagService = TestBed.inject(TagService);
|
tagService = TestBed.inject(TagService);
|
||||||
notificationService = TestBed.inject(NotificationService);
|
notificationService = TestBed.inject(NotificationService);
|
||||||
|
testingUtils = new UnitTestingUtils(fixture.debugElement, loader);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -75,7 +76,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
* @returns native element
|
* @returns native element
|
||||||
*/
|
*/
|
||||||
function getNameInput(): HTMLInputElement {
|
function getNameInput(): HTMLInputElement {
|
||||||
return fixture.debugElement.query(By.css(`.adf-tag-name-field input`))?.nativeElement;
|
return testingUtils.getInputByCSS('.adf-tag-name-field input');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,7 +85,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
* @returns native element
|
* @returns native element
|
||||||
*/
|
*/
|
||||||
function getCreateTagLabel(): HTMLSpanElement {
|
function getCreateTagLabel(): HTMLSpanElement {
|
||||||
return fixture.debugElement.query(By.css('.adf-create-tag-label'))?.nativeElement;
|
return testingUtils.getByCSS('.adf-create-tag-label')?.nativeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,8 +94,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
* @returns list of native elements
|
* @returns list of native elements
|
||||||
*/
|
*/
|
||||||
function getRemoveTagButtons(): HTMLButtonElement[] {
|
function getRemoveTagButtons(): HTMLButtonElement[] {
|
||||||
const elements = fixture.debugElement.queryAll(By.css(`.adf-dynamic-chip-list-delete-icon`));
|
return testingUtils.getAllByCSS('.adf-dynamic-chip-list-delete-btn').map((el) => el.nativeElement);
|
||||||
return elements.map((el) => el.nativeElement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,7 +122,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
typeTag(tagName, typingTimeout);
|
typeTag(tagName, typingTimeout);
|
||||||
|
|
||||||
if (addUsingEnter) {
|
if (addUsingEnter) {
|
||||||
getNameInput().dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
|
testingUtils.keyBoardEventByCSS('.adf-tag-name-field input', 'keyup', 'Enter', 'Enter');
|
||||||
} else {
|
} else {
|
||||||
getCreateTagLabel().click();
|
getCreateTagLabel().click();
|
||||||
}
|
}
|
||||||
@@ -141,9 +141,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
component.tagNameControlVisible = true;
|
component.tagNameControlVisible = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const tagNameInput = getNameInput();
|
testingUtils.fillInputByCSS('.adf-tag-name-field input', tagName);
|
||||||
tagNameInput.value = tagName;
|
|
||||||
tagNameInput.dispatchEvent(new InputEvent('input'));
|
|
||||||
|
|
||||||
tick(timeout);
|
tick(timeout);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -155,12 +153,12 @@ describe('TagsCreatorComponent', () => {
|
|||||||
* @returns label
|
* @returns label
|
||||||
*/
|
*/
|
||||||
function getExistingTagsLabel(): string {
|
function getExistingTagsLabel(): string {
|
||||||
return fixture.debugElement.query(By.css('.adf-existing-tags-label')).nativeElement.textContent.trim();
|
return testingUtils.getByCSS('.adf-existing-tags-label').nativeElement.textContent.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Created tags list', () => {
|
describe('Created tags list', () => {
|
||||||
it('should display no tags created message after initialization', () => {
|
it('should display no tags created message after initialization', () => {
|
||||||
const message = fixture.debugElement.query(By.css('.adf-no-tags-message')).nativeElement.textContent.trim();
|
const message = testingUtils.getByCSS('.adf-no-tags-message').nativeElement.textContent.trim();
|
||||||
expect(message).toBe('TAG.TAGS_CREATOR.NO_TAGS_CREATED');
|
expect(message).toBe('TAG.TAGS_CREATOR.NO_TAGS_CREATED');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -271,6 +269,51 @@ describe('TagsCreatorComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(await getAddedTags()).toEqual(component.tags);
|
expect(await getAddedTags()).toEqual(component.tags);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should focus input when last tag is removed', fakeAsync(() => {
|
||||||
|
addTagToAddedList('Tag 1');
|
||||||
|
|
||||||
|
const input = getNameInput();
|
||||||
|
spyOn(input, 'focus');
|
||||||
|
|
||||||
|
getRemoveTagButtons()[0].click();
|
||||||
|
tick();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(input.focus).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should focus button at same index when a non-last tag is removed', fakeAsync(() => {
|
||||||
|
addTagToAddedList('Tag 1');
|
||||||
|
addTagToAddedList('Tag 2');
|
||||||
|
|
||||||
|
getRemoveTagButtons()[0].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const remainingButton = getRemoveTagButtons()[0];
|
||||||
|
spyOn(remainingButton, 'focus');
|
||||||
|
|
||||||
|
tick();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(remainingButton.focus).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should focus previous button when last tag in list is removed', fakeAsync(() => {
|
||||||
|
addTagToAddedList('Tag 1');
|
||||||
|
addTagToAddedList('Tag 2');
|
||||||
|
|
||||||
|
getRemoveTagButtons()[1].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const remainingButton = getRemoveTagButtons()[0];
|
||||||
|
spyOn(remainingButton, 'focus');
|
||||||
|
|
||||||
|
tick();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(remainingButton.focus).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Tag name field', () => {
|
describe('Tag name field', () => {
|
||||||
@@ -316,16 +359,14 @@ describe('TagsCreatorComponent', () => {
|
|||||||
* @returns error text
|
* @returns error text
|
||||||
*/
|
*/
|
||||||
function getFirstError(): string {
|
function getFirstError(): string {
|
||||||
const error = fixture.debugElement.query(By.directive(MatError));
|
return testingUtils.getByDirective(MatError)?.nativeElement.textContent.trim();
|
||||||
return error?.nativeElement.textContent.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should show error for only spaces', fakeAsync(() => {
|
it('should show error for only spaces', fakeAsync(() => {
|
||||||
typeTag(' ');
|
typeTag(' ');
|
||||||
component.tagNameControl.markAsTouched();
|
component.tagNameControl.markAsTouched();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const error = getFirstError();
|
expect(getFirstError()).toBe('TAG.TAGS_CREATOR.ERRORS.EMPTY_TAG');
|
||||||
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.EMPTY_TAG');
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should show error for only spaces if tags are changed', fakeAsync(() => {
|
it('should show error for only spaces if tags are changed', fakeAsync(() => {
|
||||||
@@ -342,8 +383,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
addTagToAddedList(tag);
|
addTagToAddedList(tag);
|
||||||
typeTag(tag);
|
typeTag(tag);
|
||||||
|
|
||||||
const error = getFirstError();
|
expect(getFirstError()).toBe('TAG.TAGS_CREATOR.ERRORS.ALREADY_ADDED_TAG');
|
||||||
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.ALREADY_ADDED_TAG');
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should show error when duplicated already added tag if tags are changed', fakeAsync(() => {
|
it('should show error when duplicated already added tag if tags are changed', fakeAsync(() => {
|
||||||
@@ -361,8 +401,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
typeTag('tag*"<>\\/?:|{}()^.');
|
typeTag('tag*"<>\\/?:|{}()^.');
|
||||||
component.tagNameControl.markAsTouched();
|
component.tagNameControl.markAsTouched();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const error = getFirstError();
|
expect(getFirstError()).toBe('TAG.TAGS_CREATOR.ERRORS.SPECIAL_CHARACTERS');
|
||||||
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.SPECIAL_CHARACTERS');
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should show error when duplicated already existing tag', fakeAsync(() => {
|
it('should show error when duplicated already existing tag', fakeAsync(() => {
|
||||||
@@ -378,8 +417,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
);
|
);
|
||||||
typeTag(tag);
|
typeTag(tag);
|
||||||
|
|
||||||
const error = getFirstError();
|
expect(getFirstError()).toBe('TAG.TAGS_CREATOR.ERRORS.EXISTING_TAG');
|
||||||
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.EXISTING_TAG');
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should show error when duplicated already existing tag with spaces', fakeAsync(() => {
|
it('should show error when duplicated already existing tag with spaces', fakeAsync(() => {
|
||||||
@@ -395,8 +433,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
);
|
);
|
||||||
typeTag(tag + ' ');
|
typeTag(tag + ' ');
|
||||||
|
|
||||||
const error = getFirstError();
|
expect(getFirstError()).toBe('TAG.TAGS_CREATOR.ERRORS.EXISTING_TAG');
|
||||||
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.EXISTING_TAG');
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should show error when deleting other Tag1 and Tag2 is typed and already existing tag', fakeAsync(() => {
|
it('should show error when deleting other Tag1 and Tag2 is typed and already existing tag', fakeAsync(() => {
|
||||||
@@ -418,8 +455,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
component.removeTag(tag1);
|
component.removeTag(tag1);
|
||||||
tick();
|
tick();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const error = getFirstError();
|
expect(getFirstError()).toBe('TAG.TAGS_CREATOR.ERRORS.EXISTING_TAG');
|
||||||
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.EXISTING_TAG');
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -431,7 +467,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
* @returns debug element
|
* @returns debug element
|
||||||
*/
|
*/
|
||||||
function getPanel(): DebugElement {
|
function getPanel(): DebugElement {
|
||||||
return fixture.debugElement.query(By.css(`.adf-existing-tags-panel`));
|
return testingUtils.getByCSS('.adf-existing-tags-panel');
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should be visible when input is visible and something is typed in input', fakeAsync(() => {
|
it('should be visible when input is visible and something is typed in input', fakeAsync(() => {
|
||||||
@@ -521,8 +557,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
* @returns list of tags
|
* @returns list of tags
|
||||||
*/
|
*/
|
||||||
function getExistingTags(): string[] {
|
function getExistingTags(): string[] {
|
||||||
const tagElements = fixture.debugElement.queryAll(By.css(`.adf-existing-tags-panel .adf-tag`));
|
return testingUtils.getAllByCSS('.adf-existing-tags-panel .adf-tag').map((el) => el.nativeElement.textContent.trim());
|
||||||
return tagElements.map((el) => el.nativeElement.textContent.trim());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should call findTagByName on tagService using name set in input', fakeAsync(() => {
|
it('should call findTagByName on tagService using name set in input', fakeAsync(() => {
|
||||||
@@ -571,8 +606,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
component.tagNameControl.markAsTouched();
|
component.tagNameControl.markAsTouched();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const tagElements = getExistingTags();
|
expect(getExistingTags()).toEqual([tag1, tag2]);
|
||||||
expect(tagElements).toEqual([tag1, tag2]);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should exclude tags passed through tags input from loaded existing tags', fakeAsync(() => {
|
it('should exclude tags passed through tags input from loaded existing tags', fakeAsync(() => {
|
||||||
@@ -617,8 +651,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
|
|
||||||
typeTag(tag);
|
typeTag(tag);
|
||||||
|
|
||||||
const tagElements = getExistingTags();
|
expect(getExistingTags()).toEqual([tag]);
|
||||||
expect(tagElements).toEqual([tag]);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not display exact tag if that tag was passed through tags input', fakeAsync(() => {
|
it('should not display exact tag if that tag was passed through tags input', fakeAsync(() => {
|
||||||
@@ -671,8 +704,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
);
|
);
|
||||||
typeTag(tag);
|
typeTag(tag);
|
||||||
|
|
||||||
const tagElements = getExistingTags();
|
expect(getExistingTags()).toEqual([tag, tag1, tag2]);
|
||||||
expect(tagElements).toEqual([tag, tag1, tag2]);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should selection be disabled if mode is Create', fakeAsync(() => {
|
it('should selection be disabled if mode is Create', fakeAsync(() => {
|
||||||
@@ -715,9 +747,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
* @returns debug element
|
* @returns debug element
|
||||||
*/
|
*/
|
||||||
async function getSpinner(): Promise<MatProgressSpinnerHarness> {
|
async function getSpinner(): Promise<MatProgressSpinnerHarness> {
|
||||||
const progressSpinner = await loader.getHarnessOrNull(MatProgressSpinnerHarness);
|
return loader.getHarnessOrNull(MatProgressSpinnerHarness);
|
||||||
|
|
||||||
return progressSpinner;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should be displayed when existing tags are loading', fakeAsync(async () => {
|
it('should be displayed when existing tags are loading', fakeAsync(async () => {
|
||||||
@@ -725,8 +755,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
component.tagNameControl.markAsTouched();
|
component.tagNameControl.markAsTouched();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const spinner = await getSpinner();
|
expect(await getSpinner()).toBeTruthy();
|
||||||
expect(spinner).toBeTruthy();
|
|
||||||
|
|
||||||
discardPeriodicTasks();
|
discardPeriodicTasks();
|
||||||
flush();
|
flush();
|
||||||
@@ -735,8 +764,7 @@ describe('TagsCreatorComponent', () => {
|
|||||||
it('should not be displayed when existing tags stopped loading', fakeAsync(async () => {
|
it('should not be displayed when existing tags stopped loading', fakeAsync(async () => {
|
||||||
typeTag('tag');
|
typeTag('tag');
|
||||||
|
|
||||||
const spinner = await getSpinner();
|
expect(await getSpinner()).toBeFalsy();
|
||||||
expect(spinner).toBeFalsy();
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should have correct diameter', fakeAsync(async () => {
|
it('should have correct diameter', fakeAsync(async () => {
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
|||||||
private readonly tagsListElement: ElementRef;
|
private readonly tagsListElement: ElementRef;
|
||||||
@ViewChild('tagNameInput')
|
@ViewChild('tagNameInput')
|
||||||
private readonly tagNameInputElement: ElementRef;
|
private readonly tagNameInputElement: ElementRef;
|
||||||
|
@ViewChild(DynamicChipListComponent)
|
||||||
|
private readonly dynamicChipList: DynamicChipListComponent;
|
||||||
|
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
@@ -271,7 +273,7 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
|||||||
* or if user is still typing what means that validation for input is not called yet.
|
* or if user is still typing what means that validation for input is not called yet.
|
||||||
*/
|
*/
|
||||||
addTag(): void {
|
addTag(): void {
|
||||||
if (!this._typing && !this.tagNameControl.invalid) {
|
if (!this._typing && !this.tagNameControl.invalid && this.tagNameControl.value.trim()) {
|
||||||
this.tags = [...this.tags, this.tagNameControl.value.trim()];
|
this.tags = [...this.tags, this.tagNameControl.value.trim()];
|
||||||
this.clearTagNameInput();
|
this.clearTagNameInput();
|
||||||
this.checkScrollbarVisibility();
|
this.checkScrollbarVisibility();
|
||||||
@@ -286,6 +288,7 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
|||||||
* @param tag tag's name which should be removed from top list.
|
* @param tag tag's name which should be removed from top list.
|
||||||
*/
|
*/
|
||||||
removeTag(tag: string): void {
|
removeTag(tag: string): void {
|
||||||
|
const removedIndex = this.tagsToDisplay.findIndex((chip) => chip.id === tag);
|
||||||
this.removeTagFromArray(this.tags, tag);
|
this.removeTagFromArray(this.tags, tag);
|
||||||
this.tags = [...this.tags];
|
this.tags = [...this.tags];
|
||||||
this.tagNameControl.updateValueAndValidity();
|
this.tagNameControl.updateValueAndValidity();
|
||||||
@@ -293,6 +296,7 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
|||||||
this.exactTagSet$.next();
|
this.exactTagSet$.next();
|
||||||
this.checkScrollbarVisibility();
|
this.checkScrollbarVisibility();
|
||||||
this.tagsChange.emit(this.tags);
|
this.tagsChange.emit(this.tags);
|
||||||
|
setTimeout(() => this.focusAfterRemoval(removedIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -442,4 +446,12 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
|||||||
this.tagNameControl.setValue('');
|
this.tagNameControl.setValue('');
|
||||||
this.tagNameControl.markAsUntouched();
|
this.tagNameControl.markAsUntouched();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private focusAfterRemoval(removedIndex: number): void {
|
||||||
|
if (this.tags.length === 0) {
|
||||||
|
this.tagNameInputElement?.nativeElement?.focus();
|
||||||
|
} else {
|
||||||
|
this.dynamicChipList?.focusDeleteButton(removedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,24 @@
|
|||||||
[style.border-radius]="roundUpChips ? '20px' : '10px'"
|
[style.border-radius]="roundUpChips ? '20px' : '10px'"
|
||||||
[style.font-weight]="'bold'"
|
[style.font-weight]="'bold'"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
[attr.aria-label]="chip.name"
|
[attr.aria-label]="chip.name">
|
||||||
(removed)="removedChip.emit(chip.id)">
|
<div class="adf-dynamic-chip-list-content">
|
||||||
<span id="adf-dynamic-chip-list-chip-name-{{ idx }}">{{ chip.name }}</span>
|
<span id="adf-dynamic-chip-list-chip-name-{{ idx }}">{{ chip.name }}</span>
|
||||||
<mat-icon
|
<button type="button"
|
||||||
*ngIf="showDelete"
|
*ngIf="showDelete"
|
||||||
id="adf-dynamic-chip-list-delete-{{ chip.name }}"
|
[disabled]="disableDelete"
|
||||||
class="adf-dynamic-chip-list-delete-icon"
|
(click)="removedChip.emit(chip.id)"
|
||||||
[disabled]="disableDelete"
|
[attr.data-automation-id]="'adf-dynamic-chip-list-delete-btn-' + chip.id"
|
||||||
adf-icon="close"
|
class="adf-dynamic-chip-list-delete-btn"
|
||||||
matChipRemove
|
[attr.aria-label]="'DYNAMIC_CHIP_LIST.DELETE' | translate: { name: chip.name }"
|
||||||
/>
|
[title]="'DYNAMIC_CHIP_LIST.DELETE' | translate: { name: chip.name }">
|
||||||
|
<mat-icon
|
||||||
|
id="adf-dynamic-chip-list-delete-{{ chip.name }}"
|
||||||
|
class="adf-dynamic-chip-list-delete-icon"
|
||||||
|
adf-icon="close"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</mat-chip>
|
</mat-chip>
|
||||||
</mat-chip-set>
|
</mat-chip-set>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -7,6 +7,35 @@
|
|||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
|
|
||||||
|
.adf-dynamic-chip-list-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-dynamic-chip-list-delete-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
margin: -10px -14px -10px -2px;
|
||||||
|
padding: 10px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline-offset: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-dynamic-chip-list-delete-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.adf-dynamic-chip-list-view-more-button {
|
.adf-dynamic-chip-list-view-more-button {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ describe('DynamicChipListComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
testingUtils.clickByCSS('#adf-dynamic-chip-list-delete-test1');
|
testingUtils.clickByDataAutomationId('adf-dynamic-chip-list-delete-btn-0ee933fa-57fc-4587-8a77-b787e814f1d2');
|
||||||
|
|
||||||
expect(component.removedChip.emit).toHaveBeenCalledWith('0ee933fa-57fc-4587-8a77-b787e814f1d2');
|
expect(component.removedChip.emit).toHaveBeenCalledWith('0ee933fa-57fc-4587-8a77-b787e814f1d2');
|
||||||
});
|
});
|
||||||
@@ -149,7 +149,7 @@ describe('DynamicChipListComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
const chip = testingUtils.getByCSS('.adf-dynamic-chip-list-delete-icon');
|
const chip = testingUtils.getByCSS('.adf-dynamic-chip-list-delete-btn');
|
||||||
expect(Object.keys(chip.attributes)).toContain('disabled');
|
expect(Object.keys(chip.attributes)).toContain('disabled');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -159,6 +159,12 @@ export class DynamicChipListComponent implements OnChanges, OnInit, AfterViewIni
|
|||||||
this.displayNext.emit();
|
this.displayNext.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusDeleteButton(index: number): void {
|
||||||
|
const buttons: NodeListOf<HTMLButtonElement> = this.containerView.nativeElement.querySelectorAll('.adf-dynamic-chip-list-delete-btn');
|
||||||
|
const target: HTMLButtonElement = buttons[index] ?? buttons[index - 1];
|
||||||
|
target?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
private calculateChipsToDisplay(): void {
|
private calculateChipsToDisplay(): void {
|
||||||
if (this.requestedDisplayingAllChips || !this.chips.length) {
|
if (this.requestedDisplayingAllChips || !this.chips.length) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -659,7 +659,8 @@
|
|||||||
"ICON": "Node Icon"
|
"ICON": "Node Icon"
|
||||||
},
|
},
|
||||||
"DYNAMIC_CHIP_LIST": {
|
"DYNAMIC_CHIP_LIST": {
|
||||||
"LOAD_MORE": "Load more"
|
"LOAD_MORE": "Load more",
|
||||||
|
"DELETE": "Remove {{ name }}"
|
||||||
},
|
},
|
||||||
"ADF_CONFIRM_DIALOG": {
|
"ADF_CONFIRM_DIALOG": {
|
||||||
"TITLE": "Confirm",
|
"TITLE": "Confirm",
|
||||||
|
|||||||
Reference in New Issue
Block a user