Skip to content

Commit 54d5974

Browse files
authored
Merge branch 'master' into sstoychev/eslint-errors
2 parents 5817a7a + af069e3 commit 54d5974

File tree

8 files changed

+109
-98
lines changed

8 files changed

+109
-98
lines changed

projects/igniteui-angular/src/lib/carousel/carousel.component.html

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,32 @@
2121
</a>
2222
</ng-template>
2323

24-
25-
<div *ngIf="showIndicators" [ngClass]="indicatorsOrientationClass">
24+
<div *ngIf="showIndicators" [ngClass]="indicatorsOrientationClass" [attr.role]="'tablist'">
2625
<div *ngFor="let slide of slides"
2726
class="igx-carousel-indicators__indicator"
2827
(click)="select(slide)"
29-
[attr.aria-label]="setAriaLabel(slide)"
28+
[id]="'tab-'+ slide.index + '-' + total"
29+
[attr.role]="'tab'"
30+
[attr.aria-label]="resourceStrings.igx_carousel_slide + ' ' + (slide.index + 1) + ' ' + resourceStrings.igx_carousel_of + ' ' + this.total"
31+
[attr.aria-controls]="'panel-' + slide.index"
3032
[attr.aria-selected]="slide.active">
3133
<ng-container *ngTemplateOutlet="getIndicatorTemplate; context: {$implicit: slide};"></ng-container>
3234
</div>
3335
</div>
3436

3537
<div *ngIf="showIndicatorsLabel" [ngClass]="indicatorsOrientationClass">
36-
<span class="igx-carousel__label">{{getCarouselLabel}}</span>
38+
<span [id]="labelId" class="igx-carousel__label">{{getCarouselLabel}}</span>
3739
</div>
3840

39-
<div class="igx-carousel__inner" role="list">
41+
<div class="igx-carousel__inner" [attr.aria-live]="!interval || stoppedByInteraction ? 'polite' : 'off'">
4042
<ng-content></ng-content>
4143
</div>
4244

43-
<div *ngIf="navigation && slides.length" role="button" tabindex="0" class="igx-carousel__arrow--prev" (click)="prev()">
45+
<div *ngIf="navigation && slides.length" role="button" tabindex="0" class="igx-carousel__arrow--prev" [attr.aria-label]="resourceStrings.igx_carousel_previous_slide" (keydown.enter)="prev()" (click)="prev()">
4446
<ng-container *ngTemplateOutlet="getPrevButtonTemplate; context: {$implicit: prevButtonDisabled};"></ng-container>
4547
</div>
4648

47-
<div *ngIf="navigation && slides.length" role="button" tabindex="0" class="igx-carousel__arrow--next" (click)="next()">
49+
<div *ngIf="navigation && slides.length" role="button" tabindex="0" class="igx-carousel__arrow--next" [attr.aria-label]="resourceStrings.igx_carousel_next_slide" (keydown.enter)="next()" (click)="next()">
4850
<ng-container *ngTemplateOutlet="getNextButtonTemplate; context: {$implicit: nextButtonDisabled};"></ng-container>
4951
</div>
5052

projects/igniteui-angular/src/lib/carousel/carousel.component.spec.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,41 @@ describe('Carousel', () => {
464464
});
465465

466466

467+
it('should apply correctly aria attributes to carousel component', () => {
468+
const expectedRole = 'region';
469+
const expectedRoleDescription = 'carousel';
470+
const tabIndex = carousel.nativeElement.getAttribute('tabindex');
471+
472+
expect(tabIndex).toBeNull();
473+
expect(carousel.nativeElement.getAttribute('role')).toEqual(expectedRole);
474+
expect(carousel.nativeElement.getAttribute('aria-roledescription')).toEqual(expectedRoleDescription);
475+
476+
const indicators = carousel.nativeElement.querySelector(HelperTestFunctions.INDICATORS_BOTTOM_CLASS);
477+
478+
expect(indicators).toBeDefined();
479+
expect(indicators.getAttribute('role')).toEqual('tablist');
480+
481+
const tabs = carousel.nativeElement.querySelectorAll('[role="tab"]');
482+
expect(tabs.length).toEqual(4);
483+
});
484+
485+
it('should apply correctly aria attributes to slide components', () => {
486+
carousel.loop = false;
487+
carousel.select(carousel.get(1));
488+
fixture.detectChanges();
489+
490+
const expectedRole = 'tabpanel';
491+
const slide = carousel.slides.find(s => s.active);
492+
const tabIndex = slide.nativeElement.getAttribute('tabindex');
493+
494+
expect(+tabIndex).toBe(0);
495+
expect(slide.nativeElement.getAttribute('role')).toEqual(expectedRole);
496+
497+
const tabs = carousel.nativeElement.querySelectorAll('[role="tab"]');
498+
const slides = carousel.nativeElement.querySelectorAll('[role="tabpanel"]');
499+
500+
expect(slides.length).toEqual(tabs.length);
501+
});
467502
});
468503

469504
describe('Templates Tests: ', () => {
@@ -887,7 +922,7 @@ class HelperTestFunctions {
887922
deltaY: 0,
888923
duration: 100,
889924
velocity,
890-
preventDefault: ( ( e: any ) => { })
925+
preventDefault: ( () => { })
891926
};
892927

893928
carouselElement.triggerEventHandler(event, panOptions);
@@ -1013,7 +1048,7 @@ class CarouselDynamicSlidesComponent {
10131048
this.addNewSlide();
10141049
}
10151050

1016-
addNewSlide() {
1051+
public addNewSlide() {
10171052
this.slides.push(
10181053
{ text: 'Slide 1', active: false },
10191054
{ text: 'Slide 2', active: false },

projects/igniteui-angular/src/lib/carousel/carousel.component.ts

Lines changed: 30 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,6 @@ export class CarouselHammerConfig extends HammerGestureConfig {
9696
})
9797

9898
export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
99-
/**
100-
* Returns the `role` attribute of the carousel.
101-
* ```typescript
102-
* let carouselRole = this.carousel.role;
103-
* ```
104-
*
105-
* @memberof IgxCarouselComponent
106-
*/
107-
@HostBinding('attr.role') public role = 'region';
10899

109100
/**
110101
* Sets the `id` of the carousel.
@@ -118,29 +109,24 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
118109
@HostBinding('attr.id')
119110
@Input()
120111
public id = `igx-carousel-${NEXT_ID++}`;
121-
122112
/**
123-
* Returns the `aria-label` of the carousel.
124-
*
125-
* ```typescript
126-
* let carousel = this.carousel.ariaLabel;
127-
* ```
128-
*
129-
*/
130-
@HostBinding('attr.aria-label')
131-
public ariaLabel = 'carousel';
132-
133-
/**
134-
* Returns the `tabIndex` of the carousel component.
113+
* Returns the `role` attribute of the carousel.
135114
* ```typescript
136-
* let tabIndex = this.carousel.tabIndex;
115+
* let carouselRole = this.carousel.role;
137116
* ```
138117
*
139118
* @memberof IgxCarouselComponent
140119
*/
141-
@HostBinding('attr.tabindex')
142-
get tabIndex() {
143-
return 0;
120+
@HostBinding('attr.role') public role = 'region';
121+
122+
/** @hidden */
123+
@HostBinding('attr.aria-roledescription')
124+
public roleDescription = 'carousel';
125+
126+
/** @hidden */
127+
@HostBinding('attr.aria-labelledby')
128+
public get labelId() {
129+
return this.showIndicatorsLabel ? `${this.id}-label` : null;
144130
}
145131

146132
/**
@@ -161,7 +147,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
161147
* ```
162148
*/
163149
@HostBinding('style.touch-action')
164-
get touchAction() {
150+
public get touchAction() {
165151
return this.gesturesSupport ? 'pan-y' : 'auto';
166152
}
167153

@@ -398,11 +384,15 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
398384
@ViewChild('defaultPrevButton', { read: TemplateRef, static: true })
399385
private defaultPrevButton: TemplateRef<any>;
400386

387+
/**
388+
* @hidden
389+
* @internal
390+
*/
391+
public stoppedByInteraction: boolean;
401392
private _interval: number;
402393
private _resourceStrings = CurrentResourceStrings.CarouselResStrings;
403394
private lastInterval: any;
404395
private playing: boolean;
405-
private stoppedByInteraction: boolean;
406396
private destroyed: boolean;
407397
private destroy$ = new Subject<any>();
408398
private differ: IterableDiffer<IgxSlideComponent> | null = null;
@@ -420,14 +410,14 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
420410
* By default it uses EN resources.
421411
*/
422412
@Input()
423-
set resourceStrings(value: ICarouselResourceStrings) {
413+
public set resourceStrings(value: ICarouselResourceStrings) {
424414
this._resourceStrings = Object.assign({}, this._resourceStrings, value);
425415
}
426416

427417
/**
428418
* An accessor that returns the resource strings.
429419
*/
430-
get resourceStrings(): ICarouselResourceStrings {
420+
public get resourceStrings(): ICarouselResourceStrings {
431421
return this._resourceStrings;
432422
}
433423

@@ -484,7 +474,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
484474
* @memberOf IgxCarouselComponent
485475
*/
486476
public get total(): number {
487-
return this.slides.length;
477+
return this.slides?.length;
488478
}
489479

490480
/**
@@ -530,7 +520,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
530520
*
531521
* @memberof IgxCarouselComponent
532522
*/
533-
get nativeElement(): any {
523+
public get nativeElement(): any {
534524
return this.element.nativeElement;
535525
}
536526

@@ -543,7 +533,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
543533
* @memberof IgxCarouselComponent
544534
*/
545535
@Input()
546-
get interval(): number {
536+
public get interval(): number {
547537
return this._interval;
548538
}
549539

@@ -556,7 +546,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
556546
*
557547
* @memberof IgxCarouselComponent
558548
*/
559-
set interval(value: number) {
549+
public set interval(value: number) {
560550
this._interval = +value;
561551
this.restartInterval();
562552
}
@@ -566,13 +556,14 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
566556
this.differ = this.iterableDiffers.find([]).create(null);
567557
}
568558

559+
569560
/** @hidden */
570561
@HostListener('keydown.arrowright', ['$event'])
571562
public onKeydownArrowRight(event) {
572563
if (this.keyboardSupport) {
573564
event.preventDefault();
574565
this.next();
575-
requestAnimationFrame(() => this.nativeElement.focus());
566+
requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
576567
}
577568
}
578569

@@ -582,7 +573,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
582573
if (this.keyboardSupport) {
583574
event.preventDefault();
584575
this.prev();
585-
requestAnimationFrame(() => this.nativeElement.focus());
576+
requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
586577
}
587578
}
588579

@@ -608,7 +599,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
608599
if (this.keyboardSupport && this.slides.length > 0) {
609600
event.preventDefault();
610601
this.slides.first.active = true;
611-
requestAnimationFrame(() => this.nativeElement.focus());
602+
requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
612603
}
613604
}
614605

@@ -618,7 +609,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
618609
if (this.keyboardSupport && this.slides.length > 0) {
619610
event.preventDefault();
620611
this.slides.last.active = true;
621-
requestAnimationFrame(() => this.nativeElement.focus());
612+
requestAnimationFrame(() => this.slides.find(s => s.active).nativeElement.focus());
622613
}
623614
}
624615

@@ -713,11 +704,6 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
713704
}
714705
}
715706

716-
/** @hidden */
717-
public setAriaLabel(slide) {
718-
return `Item ${slide.index + 1} of ${this.total}`;
719-
}
720-
721707
/**
722708
* Returns the slide corresponding to the provided `index` or null.
723709
* ```typescript
@@ -1104,6 +1090,7 @@ export class IgxCarouselComponent implements OnDestroy, AfterContentInit {
11041090
this.slides.reduce((any, c, ind) => c.index = ind, 0); // reset slides indexes
11051091
diff.forEachAddedItem((record: IterableChangeRecord<IgxSlideComponent>) => {
11061092
const slide = record.item;
1093+
slide.total = this.total;
11071094
this.onSlideAdded.emit({ carousel: this, slide });
11081095
if (slide.active) {
11091096
this.currentSlide = slide;

projects/igniteui-angular/src/lib/carousel/carousel.directives.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Directive, TemplateRef } from '@angular/core';
1+
import { Directive } from '@angular/core';
22

33
@Directive({
44
selector: '[igxCarouselIndicator]'

projects/igniteui-angular/src/lib/carousel/slide.component.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, OnDestroy, Input, HostBinding, Output, EventEmitter, ElementRef, ChangeDetectorRef } from '@angular/core';
1+
import { Component, OnDestroy, Input, HostBinding, Output, EventEmitter, ElementRef, AfterContentChecked } from '@angular/core';
22
import { Subject } from 'rxjs';
33

44
export enum Direction { NONE, NEXT, PREV }
@@ -20,7 +20,7 @@ export enum Direction { NONE, NEXT, PREV }
2020
templateUrl: 'slide.component.html'
2121
})
2222

23-
export class IgxSlideComponent implements OnDestroy {
23+
export class IgxSlideComponent implements AfterContentChecked, OnDestroy {
2424
/**
2525
* Gets/sets the `index` of the slide inside the carousel.
2626
* ```html
@@ -45,6 +45,9 @@ export class IgxSlideComponent implements OnDestroy {
4545
*/
4646
@Input() public direction: Direction;
4747

48+
@Input()
49+
public total: number;
50+
4851
/**
4952
* Returns the `tabIndex` of the slide component.
5053
* ```typescript
@@ -54,35 +57,28 @@ export class IgxSlideComponent implements OnDestroy {
5457
* @memberof IgxSlideComponent
5558
*/
5659
@HostBinding('attr.tabindex')
57-
get tabIndex() {
60+
public get tabIndex() {
5861
return this.active ? 0 : null;
5962
}
6063

6164
/**
62-
* Returns the `aria-selected` of the slide.
63-
*
64-
* ```typescript
65-
* let slide = this.slide.ariaSelected;
66-
* ```
67-
*
65+
* @hidden
6866
*/
69-
@HostBinding('attr.aria-selected')
70-
public get ariaSelected(): boolean {
71-
return this.active;
72-
}
67+
@HostBinding('attr.id')
68+
public id: string;
7369

7470
/**
75-
* Returns the `aria-live` of the slide.
76-
*
77-
* ```typescript
78-
* let slide = this.slide.ariaLive;
79-
* ```
71+
* Returns the `role` of the slide component.
72+
* By default is set to `tabpanel`
8073
*
74+
* @memberof IgxSlideComponent
8175
*/
82-
@HostBinding('attr.aria-selected')
83-
public get ariaLive() {
84-
return this.active ? 'polite' : null;
85-
}
76+
@HostBinding('attr.role')
77+
public tab = 'tabpanel';
78+
79+
/** @hidden */
80+
@HostBinding('attr.aria-labelledby')
81+
public ariaLabelledBy;
8682

8783
/**
8884
* Returns the class of the slide component.
@@ -155,6 +151,11 @@ export class IgxSlideComponent implements OnDestroy {
155151
return this._destroy$;
156152
}
157153

154+
public ngAfterContentChecked() {
155+
this.id = `panel-${this.index}`;
156+
this.ariaLabelledBy = `tab-${this.index}-${this.total}`;
157+
}
158+
158159
/**
159160
* @hidden
160161
*/

0 commit comments

Comments
 (0)