Skip to content

Commit 7050f17

Browse files
committed
fix(dropdown): add aria-expanded attribute, refactor
1 parent 3150f32 commit 7050f17

File tree

2 files changed

+42
-29
lines changed

2 files changed

+42
-29
lines changed

projects/coreui-angular/src/lib/dropdown/dropdown/dropdown.component.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ describe('DropdownComponent', () => {
1212
beforeEach(async () => {
1313
await TestBed.configureTestingModule({
1414
imports: [DropdownComponent]
15-
})
16-
.compileComponents();
15+
}).compileComponents();
1716
});
1817

1918
beforeEach(() => {
@@ -41,7 +40,6 @@ class MockElementRef extends ElementRef {}
4140
class TestComponent {}
4241

4342
describe('DropdownToggleDirective', () => {
44-
4543
let component: TestComponent;
4644
let fixture: ComponentFixture<TestComponent>;
4745
let elementRef: DebugElement;
@@ -66,7 +64,9 @@ describe('DropdownToggleDirective', () => {
6664
});
6765

6866
it('should create an instance', () => {
69-
const directive = new DropdownToggleDirective(elementRef, service);
70-
expect(directive).toBeTruthy();
67+
TestBed.runInInjectionContext(() => {
68+
const directive = new DropdownToggleDirective();
69+
expect(directive).toBeTruthy();
70+
});
7171
});
7272
});

projects/coreui-angular/src/lib/dropdown/dropdown/dropdown.component.ts

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1+
import { DOCUMENT } from '@angular/common';
12
import {
23
AfterContentInit,
34
AfterViewInit,
45
booleanAttribute,
56
ChangeDetectorRef,
67
Component,
78
ContentChild,
9+
DestroyRef,
810
Directive,
911
ElementRef,
1012
EventEmitter,
1113
forwardRef,
1214
HostBinding,
1315
HostListener,
16+
inject,
1417
Inject,
1518
Input,
1619
NgZone,
1720
OnChanges,
1821
OnDestroy,
1922
OnInit,
20-
Optional,
2123
Output,
2224
Renderer2,
25+
signal,
2326
SimpleChanges
2427
} from '@angular/core';
25-
import { DOCUMENT } from '@angular/common';
28+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2629
import { Subscription } from 'rxjs';
2730
import { filter } from 'rxjs/operators';
2831

@@ -42,12 +45,11 @@ export abstract class DropdownToken {}
4245
standalone: true
4346
})
4447
export class DropdownToggleDirective implements AfterViewInit {
45-
46-
constructor(
47-
public elementRef: ElementRef,
48-
private dropdownService: DropdownService,
49-
@Optional() public dropdown?: DropdownToken
50-
) {}
48+
// injections
49+
readonly #destroyRef = inject(DestroyRef);
50+
public readonly elementRef = inject(ElementRef);
51+
#dropdownService = inject(DropdownService);
52+
public dropdown = inject(DropdownToken, { optional: true });
5153

5254
/**
5355
* Toggle the disabled state for the toggler.
@@ -70,7 +72,8 @@ export class DropdownToggleDirective implements AfterViewInit {
7072
@Input() caret = true;
7173

7274
/**
73-
* Create split button dropdowns with virtually the same markup as single button dropdowns, but with the addition of `.dropdown-toggle-split` class for proper spacing around the dropdown caret.
75+
* Create split button dropdowns with virtually the same markup as single button dropdowns,
76+
* but with the addition of `.dropdown-toggle-split` class for proper spacing around the dropdown caret.
7477
* @type boolean
7578
* @default false
7679
*/
@@ -85,16 +88,29 @@ export class DropdownToggleDirective implements AfterViewInit {
8588
};
8689
}
8790

91+
#ariaExpanded = signal(false);
92+
93+
@HostBinding('attr.aria-expanded')
94+
get ariaExpanded() {
95+
return this.#ariaExpanded();
96+
}
97+
8898
@HostListener('click', ['$event'])
8999
public onClick($event: MouseEvent): void {
90100
$event.preventDefault();
91-
!this.disabled && this.dropdownService.toggle({ visible: 'toggle', dropdown: this.dropdown });
101+
!this.disabled && this.#dropdownService.toggle({ visible: 'toggle', dropdown: this.dropdown });
92102
}
93103

94104
ngAfterViewInit(): void {
95105
if (this.dropdownComponent) {
96106
this.dropdown = this.dropdownComponent;
97-
this.dropdownService = this.dropdownComponent?.dropdownService;
107+
this.#dropdownService = this.dropdownComponent?.dropdownService;
108+
}
109+
if (this.dropdown) {
110+
const dropdown = <DropdownComponent>this.dropdown;
111+
dropdown?.visibleChange?.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((visible) => {
112+
this.#ariaExpanded.set(visible);
113+
});
98114
}
99115
}
100116
}
@@ -109,7 +125,6 @@ export class DropdownToggleDirective implements AfterViewInit {
109125
hostDirectives: [{ directive: ThemeDirective, inputs: ['dark'] }]
110126
})
111127
export class DropdownComponent implements AfterContentInit, OnChanges, OnDestroy, OnInit {
112-
113128
constructor(
114129
@Inject(DOCUMENT) private document: Document,
115130
private elementRef: ElementRef,
@@ -136,7 +151,8 @@ export class DropdownComponent implements AfterContentInit, OnChanges, OnDestroy
136151
@Input() direction?: 'center' | 'dropup' | 'dropup-center' | 'dropend' | 'dropstart';
137152

138153
/**
139-
* Describes the placement of your component after Popper.js has applied all the modifiers that may have flipped or altered the originally provided placement property.
154+
* Describes the placement of your component after Popper.js has applied all the modifiers
155+
* that may have flipped or altered the originally provided placement property.
140156
* @type Placement
141157
*/
142158
@Input() placement: Placement = 'bottom-start';
@@ -155,7 +171,7 @@ export class DropdownComponent implements AfterContentInit, OnChanges, OnDestroy
155171
@Input()
156172
set popperOptions(value: Partial<Options>) {
157173
this._popperOptions = { ...this._popperOptions, ...value };
158-
};
174+
}
159175

160176
get popperOptions(): Partial<Options> {
161177
let placement = this.placement;
@@ -237,12 +253,10 @@ export class DropdownComponent implements AfterContentInit, OnChanges, OnDestroy
237253
@HostBinding('class')
238254
get hostClasses(): any {
239255
return {
240-
dropdown:
241-
(this.variant === 'dropdown' || this.variant === 'nav-item') &&
242-
!this.direction,
256+
dropdown: (this.variant === 'dropdown' || this.variant === 'nav-item') && !this.direction,
243257
[`${this.direction}`]: !!this.direction,
244258
[`${this.variant}`]: !!this.variant,
245-
'dropup': this.direction === 'dropup' || this.direction === 'dropup-center',
259+
dropup: this.direction === 'dropup' || this.direction === 'dropup-center',
246260
show: this.visible
247261
};
248262
}
@@ -262,16 +276,15 @@ export class DropdownComponent implements AfterContentInit, OnChanges, OnDestroy
262276

263277
dropdownStateSubscribe(subscribe: boolean = true): void {
264278
if (subscribe) {
265-
this.dropdownStateSubscription =
266-
this.dropdownService.dropdownState$.pipe(
279+
this.dropdownStateSubscription = this.dropdownService.dropdownState$
280+
.pipe(
267281
filter((state) => {
268282
return this === state.dropdown;
269283
})
270-
).subscribe((state) => {
284+
)
285+
.subscribe((state) => {
271286
if ('visible' in state) {
272-
state?.visible === 'toggle'
273-
? this.toggleDropdown()
274-
: (this.visible = state.visible);
287+
state?.visible === 'toggle' ? this.toggleDropdown() : (this.visible = state.visible);
275288
}
276289
});
277290
} else {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy