Skip to content

Commit 2603882

Browse files
authored
Merge branch 'master' into simeonoff/scoped-styles
2 parents 7ea1262 + b9681be commit 2603882

6 files changed

Lines changed: 117 additions & 9 deletions

File tree

.github/workflows/nodejs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818

1919
steps:
2020
- name: Checkout
21-
uses: actions/checkout@v6
21+
uses: actions/checkout@v7
2222
- name: Use Node.js 24
2323
uses: actions/setup-node@v7
2424
with:

.github/workflows/npm-publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
build:
1212
runs-on: ubuntu-latest
1313
steps:
14-
- uses: actions/checkout@v6
14+
- uses: actions/checkout@v7
1515
- uses: actions/setup-node@v7
1616
with:
1717
node-version: 24.x

.github/workflows/stale.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616

1717
steps:
18-
- uses: actions/stale@v7
18+
- uses: actions/stale@v10
1919
with:
2020
repo-token: ${{ secrets.GITHUB_TOKEN }}
2121
stale-issue-message: 'There has been no recent activity and this issue has been marked inactive.'

projects/igniteui-angular/combo/src/combo/combo-dropdown.component.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ export class IgxComboDropDownComponent extends IgxDropDownComponent implements I
2020
public combo = inject<IgxComboBase>(IGX_COMBO_COMPONENT);
2121
protected comboAPI = inject(IgxComboAPIService);
2222

23-
private _activeDescendantId: string | null = null;
24-
2523
/** @hidden @internal */
2624
@Input({ transform: booleanAttribute })
2725
public singleMode = false;

projects/igniteui-angular/drop-down/src/drop-down/drop-down.component.spec.ts

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, ViewChild, OnInit, ElementRef, ViewChildren, QueryList, ChangeDetectorRef, DOCUMENT, ChangeDetectionStrategy } from '@angular/core';
1+
import { Component, ViewChild, OnInit, ElementRef, ViewChildren, QueryList, ChangeDetectorRef, DOCUMENT, ChangeDetectionStrategy, provideZonelessChangeDetection } from '@angular/core';
22
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
33
import { By } from '@angular/platform-browser';
44
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -45,7 +45,7 @@ describe('IgxDropDown ', () => {
4545
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['markForCheck', 'detectChanges']);
4646
mockSelection.get.and.returnValue(new Set([]));
4747
const mockForOf = jasmine.createSpyObj('IgxForOfDirective', ['totalItemCount']);
48-
const mockDocument = jasmine.createSpyObj('DOCUMENT', [], { 'defaultView': { getComputedStyle: () => null }});
48+
const mockDocument = jasmine.createSpyObj('DOCUMENT', [], { 'defaultView': { getComputedStyle: () => null } });
4949

5050
beforeEach(() => {
5151
TestBed.configureTestingModule({
@@ -854,7 +854,7 @@ describe('IgxDropDown ', () => {
854854

855855
const itemToClick = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_ITEM}`))[0];
856856

857-
const event = new Event('mousedown', { });
857+
const event = new Event('mousedown', {});
858858
spyOn(event, 'preventDefault');
859859
itemToClick.triggerEventHandler('mousedown', event);
860860

@@ -1029,6 +1029,98 @@ describe('IgxDropDown ', () => {
10291029
expect(expectedScroll - acceptableDelta < scrollTop && expectedScroll + acceptableDelta > scrollTop).toBe(true);
10301030
});
10311031
});
1032+
describe('Zoneless virtualization tests', () => {
1033+
let scroll: IgxForOfDirective<any>;
1034+
beforeEach(async () => {
1035+
TestBed.resetTestingModule();
1036+
await TestBed.configureTestingModule({
1037+
imports: [
1038+
NoopAnimationsModule,
1039+
VirtualizedDropDownComponent
1040+
],
1041+
providers: [provideZonelessChangeDetection()]
1042+
}).compileComponents();
1043+
fixture = TestBed.createComponent(VirtualizedDropDownComponent);
1044+
fixture.detectChanges();
1045+
dropdown = fixture.componentInstance.dropdown;
1046+
scroll = fixture.componentInstance.virtualScroll;
1047+
});
1048+
it('should not throw when scrolling after selecting an item', async () => {
1049+
const preSelected = { value: fixture.componentInstance.items[0], index: 0 } as IgxDropDownItemBaseDirective;
1050+
dropdown.selectItem(preSelected);
1051+
1052+
dropdown.toggle();
1053+
await wait(50);
1054+
fixture.detectChanges();
1055+
1056+
scroll.getScroll().scrollTop = scroll.getScroll().scrollHeight;
1057+
await wait(50);
1058+
1059+
expect(() => fixture.detectChanges()).not.toThrow();
1060+
});
1061+
1062+
it('should update aria-activedescendant to the id of the focused item in virtualized dropdown when navigating', async () => {
1063+
const preSelected = { value: fixture.componentInstance.items[0], index: 0 } as IgxDropDownItemBaseDirective;
1064+
dropdown.selectItem(preSelected);
1065+
dropdown.toggle();
1066+
await wait(50);
1067+
fixture.detectChanges();
1068+
1069+
const targetElement = fixture.debugElement.query(By.directive(IgxButtonDirective)).nativeElement;
1070+
let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)).nativeElement;
1071+
1072+
expect(focusedItem).toBeTruthy();
1073+
let focusedItemId = focusedItem.getAttribute('id');
1074+
expect(focusedItemId).toBeTruthy();
1075+
expect(targetElement.getAttribute('aria-activedescendant')).toBe(focusedItemId);
1076+
1077+
dropdown.navigateNext();
1078+
await wait(50);
1079+
fixture.detectChanges();
1080+
1081+
focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)).nativeElement;
1082+
focusedItemId = focusedItem.getAttribute('id');
1083+
expect(targetElement.getAttribute('aria-activedescendant')).toBe(focusedItemId);
1084+
1085+
dropdown.navigateFirst();
1086+
await wait(50);
1087+
fixture.detectChanges();
1088+
focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)).nativeElement;
1089+
focusedItemId = focusedItem.getAttribute('id');
1090+
expect(targetElement.getAttribute('aria-activedescendant')).toBe(focusedItemId);
1091+
});
1092+
1093+
it('should update aria-activedescendant to the id of the focused item in virtualized dropdown when navigating with scrolling', async () => {
1094+
const preSelected = { value: fixture.componentInstance.items[0], index: 0 } as IgxDropDownItemBaseDirective;
1095+
dropdown.selectItem(preSelected);
1096+
dropdown.toggle();
1097+
await wait(50);
1098+
fixture.detectChanges();
1099+
1100+
const targetElement = fixture.debugElement.query(By.directive(IgxButtonDirective)).nativeElement;
1101+
let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)).nativeElement;
1102+
1103+
expect(focusedItem).toBeTruthy();
1104+
let focusedItemId = focusedItem.getAttribute('id');
1105+
expect(focusedItemId).toBeTruthy();
1106+
expect(targetElement.getAttribute('aria-activedescendant')).toBe(focusedItemId);
1107+
1108+
dropdown.navigateLast();
1109+
await wait(50);
1110+
fixture.detectChanges();
1111+
1112+
focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)).nativeElement;
1113+
focusedItemId = focusedItem.getAttribute('id');
1114+
expect(targetElement.getAttribute('aria-activedescendant')).toBe(focusedItemId);
1115+
1116+
dropdown.navigateFirst();
1117+
await wait(50);
1118+
fixture.detectChanges();
1119+
focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`)).nativeElement;
1120+
focusedItemId = focusedItem.getAttribute('id');
1121+
expect(targetElement.getAttribute('aria-activedescendant')).toBe(focusedItemId);
1122+
});
1123+
});
10321124
describe('Rendering', () => {
10331125
describe('Accessibility', () => {
10341126
beforeEach(waitForAsync(() => {

projects/igniteui-angular/drop-down/src/drop-down/drop-down.component.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { IgxSelectionAPIService } from 'igniteui-angular/core';
2929
import { Subject } from 'rxjs';
3030
import { IgxDropDownItemBaseDirective } from './drop-down-item.base';
3131
import { IgxForOfToken } from 'igniteui-angular/directives';
32-
import { take } from 'rxjs/operators';
32+
import { take, takeUntil } from 'rxjs/operators';
3333
import { OverlaySettings } from 'igniteui-angular/core';
3434
import { ConnectedPositioningStrategy } from 'igniteui-angular/core';
3535

@@ -61,6 +61,7 @@ import { ConnectedPositioningStrategy } from 'igniteui-angular/core';
6161
})
6262
export class IgxDropDownComponent extends IgxDropDownBaseDirective implements IDropDownBase, OnChanges, AfterViewInit, OnDestroy {
6363
protected selection = inject(IgxSelectionAPIService);
64+
protected _activeDescendantId: string | null = null;
6465

6566
/**
6667
* @hidden
@@ -174,6 +175,7 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
174175
if (!value) {
175176
this.selection.clear(`${this.id}-active`);
176177
this._focusedItem = null;
178+
this._activeDescendantId = null;
177179
return;
178180
}
179181
this._focusedItem = value;
@@ -186,6 +188,13 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
186188
this.selection.set(`${this.id}-active`, new Set([this._focusedItem]));
187189
}
188190

191+
public override get activeDescendant(): string | null {
192+
if (this.virtDir) {
193+
return this._activeDescendantId;
194+
}
195+
return super.activeDescendant;
196+
}
197+
189198
public override get id(): string {
190199
return this._id;
191200
}
@@ -338,6 +347,7 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
338347
this.skipHeader(direction);
339348
});
340349
} else {
350+
this._activeDescendantId = this.children.find(e => e.index === index)?.id ?? null;
341351
this.skipHeader(direction);
342352
}
343353
} else {
@@ -464,6 +474,13 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
464474
public ngAfterViewInit() {
465475
if (this.virtDir) {
466476
this.virtDir.igxForItemSize = 28;
477+
this.virtDir.chunkLoad.pipe(takeUntil(this.destroy$)).subscribe(() => {
478+
const item = this._focusedItem
479+
? this.children.find(e => e.index === this._focusedItem.index)
480+
: null;
481+
this._activeDescendantId = item?.id ?? null;
482+
this.cdr.markForCheck();
483+
});
467484
}
468485
}
469486

@@ -613,6 +630,7 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
613630
protected updateItemFocus() {
614631
if (this.selectedItem) {
615632
this.focusedItem = this.selectedItem;
633+
this._activeDescendantId = this.focusedItem?.id ?? null;
616634
this.focusItem(true);
617635
} else if (this.allowItemsFocus) {
618636
this.navigateFirst();

0 commit comments

Comments
 (0)