Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/components/tags/tag-list.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,41 @@ describe(KbqTagList.name, () => {
);
});

it('should synchronously set native tabIndex to -1 on tabOut to prevent focus-back loop', fakeAsync(() => {
const fixture = createStandaloneComponent(TestTagList);
const { debugElement, componentInstance } = fixture;
const tagListEl = getTagListElement(debugElement);

expect(tagListEl.tabIndex).toBe(0);

componentInstance.tagList().keyManager.onKeydown(createKeyboardEvent('keydown', TAB));

// Native DOM must be -1 immediately — before any CD cycle — so the browser does not see
// the list host as a tab stop when processing the Tab key event. Without this, sync writes
// the host holds `tabIndex=0`, and the browser re-focuses it, triggering
// (focus) → setFirstItemActive() loop (reproducible with provideZoneChangeDetection({ eventCoalescing: true })).
expect(tagListEl.tabIndex).toBe(-1);

tick();
}));
Comment thread
Copilot marked this conversation as resolved.

it('should restore tabIndex to userTabIndex after tabOut', fakeAsync(() => {
const fixture = createStandaloneComponent(TestTagList);
const { componentInstance } = fixture;
const tagList = componentInstance.tagList();

tagList.tabIndex = 3;
fixture.detectChanges();

tagList.keyManager.onKeydown(createKeyboardEvent('keydown', TAB));

expect(tagList.tabIndex).toBe(-1);

tick();

expect(tagList.tabIndex).toBe(3);
}));

it('should be draggable when draggable is enabled', () => {
const fixture = createStandaloneComponent(TestTagList);
const { debugElement, componentInstance } = fixture;
Expand Down
1 change: 1 addition & 0 deletions packages/components/tags/tag-list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ export class KbqTagList
// it back to the first tag when the user tabs out.
this.keyManager.tabOut.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this._tabIndex = -1;
this.elementRef.nativeElement.tabIndex = -1;

@artembelik artembelik Jul 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'[attr.tabindex]': 'tabIndex',

tabIndex - же применяется к хосту, зачем перезаписывать это значение? возможно дело в cdr? миграция на сигнал не решит проблему?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не решит,в режиме eventCoalescing keydown событие и focus tag-list будут объединены в группу, а CD произойдет после. Так же и при использовании сигнала

можно решить, добавив changeDetectorRef.detectChanges(), но избыточно если eventCoalescing: false. Поэтому установка tabIndex -1 - самый простой и рабочий способ


setTimeout(() => {
this._tabIndex = this.userTabIndex || 0;
Expand Down