Skip to content

Commit

Permalink
Merge branch 'develop' into feature/CXSPA-7556
Browse files Browse the repository at this point in the history
  • Loading branch information
giancorderoortiz authored Aug 19, 2024
2 parents d6433b0 + 94ebcd4 commit c01711e
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
<cx-amend-order-items *ngIf="entries$ | async as entries" [entries]="entries">
</cx-amend-order-items>

<cx-form-errors [control]="form.get('entries')"></cx-form-errors>
<cx-form-errors
*cxFeature="'!a11yRepeatedCancelOrderError'"
[control]="form.get('entries')"
></cx-form-errors>

<ng-container *ngTemplateOutlet="actions"></ng-container>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { NgModule } from '@angular/core';
import {
AuthGuard,
CmsConfig,
provideDefaultConfig,
FeaturesConfigModule,
I18nModule,
provideDefaultConfig,
} from '@spartacus/core';
import {
FormErrorsModule,
Expand All @@ -30,6 +31,7 @@ import { CancelOrderComponent } from './cancel-order.component';
AmendOrderActionsModule,
FormErrorsModule,
MessageComponentModule,
FeaturesConfigModule,
],
providers: [
provideDefaultConfig(<CmsConfig>{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,23 @@ export interface FeatureTogglesInterface {
*/
a11yLinkBtnsToTertiaryBtns?: boolean;

/**
* Aria-live inside the 'BreadcrumbComponent' will be toggled based on the active element.
* This removes the repeated announcement of the page title.
*/
a11yRepeatedPageTitleFix?: boolean;

/**
* 'NgSelectA11yDirective' will now provide a count of items for each availble option.
* Including this count in aria-label will help screen readers to provide more context to the user.
*/
a11yNgSelectOptionsCount?: boolean;

/**
* Removes duplicated error message from 'CancelOrderComponent'.
*/
a11yRepeatedCancelOrderError?: boolean;

/**
* Mofifies the template of 'AddedToCartDialogComponent' to retain the focus after the cart is updated.
* Improves its screen reader readout.
Expand Down Expand Up @@ -529,7 +546,10 @@ export const defaultFeatureToggles: Required<FeatureTogglesInterface> = {
a11yFormErrorMuteIcon: false,
a11yCxMessageFocus: false,
a11yLinkBtnsToTertiaryBtns: false,
a11yRepeatedPageTitleFix: false,
a11yDeliveryModeRadiogroup: false,
a11yNgSelectOptionsCount: false,
a11yRepeatedCancelOrderError: false,
a11yAddedToCartActiveDialog: false,
a11yNgSelectMobileReadout: false,
occCartNameAndDescriptionInHttpRequestBody: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,10 @@ if (environment.cpq) {
a11yCxMessageFocus: true,
occCartNameAndDescriptionInHttpRequestBody: true,
a11yLinkBtnsToTertiaryBtns: true,
a11yRepeatedPageTitleFix: true,
a11yDeliveryModeRadiogroup: true,
a11yNgSelectOptionsCount: true,
a11yRepeatedCancelOrderError: true,
a11yAddedToCartActiveDialog: true,
a11yNgSelectMobileReadout: true,
cmsBottomHeaderSlotUsingFlexStyles: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
tabindex="0"
>
<div class="cx-message-content">
<div class="cx-message-header">
<span class="cx-message-icon">
<cx-icon [type]="getIconType"></cx-icon>
<div role="presentation" class="cx-message-header">
<span role="presentation" class="cx-message-icon">
<cx-icon role="presentation" [type]="getIconType"></cx-icon>
</span>

<span class="cx-message-text">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
CurrencyService,
Language,
LanguageService,
TranslationService,
} from '@spartacus/core';
import { MockTranslationService } from 'projects/core/src/i18n/testing/mock-translation.service';
import { Observable, of } from 'rxjs';
import { CmsComponentData } from '../../../cms-structure/page/model/cms-component-data';
import { LanguageCurrencyComponent } from './language-currency.component';
Expand Down Expand Up @@ -104,6 +106,10 @@ describe('LanguageCurrencyComponent in CmsLib', () => {
provide: CmsComponentData,
useValue: MockCmsComponentData,
},
{
provide: TranslationService,
useClass: MockTranslationService,
},
contextServiceMapProvider,
],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<span>{{ label$ | async }}:</span>
<select (change)="active = $any($event).target.value">
<option
[attr.aria-label]="item.name"
*ngFor="let item of items"
[attr.aria-label]="ariaLabel$(item.label, i, items.length) | async"
*ngFor="let item of items; index as i"
value="{{ item.isocode }}"
[selected]="(activeItem$ | async) === item.isocode"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import {
contextServiceMapProvider,
CurrencyService,
Language,
LanguageService,
LANGUAGE_CONTEXT_ID,
LanguageService,
TranslationService,
} from '@spartacus/core';
import { MockTranslationService } from 'projects/core/src/i18n/testing/mock-translation.service';
import { Observable, of } from 'rxjs';
import { CmsComponentData } from '../../../cms-structure/page/model/cms-component-data';
import { SiteContextComponentService } from './site-context-component.service';
Expand Down Expand Up @@ -100,6 +102,10 @@ describe('SiteContextSelectorComponent in CmsLib', () => {
provide: CmsComponentData,
useValue: MockCmsComponentData,
},
{
provide: TranslationService,
useClass: MockTranslationService,
},
contextServiceMapProvider,
],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { SiteContext } from '@spartacus/core';
import { Observable } from 'rxjs';
import {
ChangeDetectionStrategy,
Component,
inject,
Input,
} from '@angular/core';
import { SiteContext, TranslationService } from '@spartacus/core';
import { map, Observable } from 'rxjs';
import { ICON_TYPE } from '../icon/icon.model';
import { SiteContextComponentService } from './site-context-component.service';
import { SiteContextType } from './site-context.model';
Expand All @@ -28,6 +33,8 @@ export class SiteContextSelectorComponent {
*/
@Input() context: SiteContextType;

protected translationService = inject(TranslationService);

constructor(private componentService: SiteContextComponentService) {}

get items$(): Observable<any> {
Expand All @@ -45,4 +52,12 @@ export class SiteContextSelectorComponent {
get label$(): Observable<any> {
return this.componentService.getLabel(this.context);
}

ariaLabel$(label: string, index: number, length: number): Observable<string> {
return this.translationService.translate('common.of').pipe(
map((translation) => {
return `${label}, ${index + 1} ${translation} ${length}`;
})
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

<!-- TODO: Remove feature flag next major release -->
<ng-container *cxFeature="'a11yPreventSRFocusOnHiddenElements'">
<h1 aria-live="polite" aria-atomic="true">{{ title$ | async }}</h1>
<h1 [attr.aria-live]="(ariaLive$ | async) ? 'polite' : null">
{{ title$ | async }}
</h1>
</ng-container>
<ng-container *cxFeature="'!a11yPreventSRFocusOnHiddenElements'">
<!-- Hidden page title for Screen Reader initialized after view to avoid old values -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
inject,
OnInit,
} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import {
CmsBreadcrumbsComponent,
FeatureConfigService,
PageMetaService,
TranslationService,
useFeatureStyles,
} from '@spartacus/core';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { combineLatest, Observable, of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { CmsComponentData } from '../../../cms-structure/page/model/cms-component-data';
import { PageTitleComponent } from '../page-header/page-title.component';

Expand All @@ -24,6 +31,20 @@ import { PageTitleComponent } from '../page-header/page-title.component';
export class BreadcrumbComponent extends PageTitleComponent implements OnInit {
crumbs$: Observable<any[]>;

protected router = inject(Router);
private featureConfigService = inject(FeatureConfigService);

ariaLive$: Observable<boolean> = this.featureConfigService.isEnabled(
'a11yRepeatedPageTitleFix'
)
? this.router.events.pipe(
filter((e) => e instanceof NavigationEnd),
map(() => {
return document.activeElement !== document.body;
})
)
: of(true);

constructor(
public component: CmsComponentData<CmsBreadcrumbsComponent>,
protected pageMetaService: PageMetaService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Component, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NgSelectModule } from '@ng-select/ng-select';
import { FeatureConfigService } from '@spartacus/core';
import { FeatureConfigService, TranslationService } from '@spartacus/core';
import { of } from 'rxjs';
import { NgSelectA11yDirective } from './ng-select-a11y.directive';
import { NgSelectA11yModule } from './ng-select-a11y.module';

Expand All @@ -28,16 +29,24 @@ class MockFeatureConfigService {
return true;
}
}

class MockTranslationService {
translate() {
return of('of');
}
}

describe('NgSelectA11yDirective', () => {
let component: MockComponent;
let fixture: ComponentFixture<MockComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgSelectA11yModule, NgSelectModule],
declarations: [MockComponent],
declarations: [MockComponent, NgSelectA11yDirective],
providers: [
{ provide: FeatureConfigService, useClass: MockFeatureConfigService },
{ provide: TranslationService, useClass: MockTranslationService },
],
}).compileComponents();

Expand All @@ -63,4 +72,23 @@ describe('NgSelectA11yDirective', () => {
expect(innerDiv.getAttribute('aria-label')).toEqual('Size');
expect(inputElement.getAttribute('aria-hidden')).toEqual('true');
});

it('should append aria-label to options', (done) => {
fixture.detectChanges();
const select = getNgSelect().nativeElement;
const ngSelectInstance = getNgSelect().componentInstance;
ngSelectInstance.open();

// Wait for the mutation observer to update the options
setTimeout(() => {
const options = select.querySelectorAll('.ng-option');
expect(options.length).toBe(3);
options.forEach((option: HTMLElement, index: number) => {
expect(option.getAttribute('aria-label')).toEqual(
`${index + 1}, ${index + 1} of ${options.length}`
);
});
done();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
AfterViewInit,
Directive,
ElementRef,
HostListener,
inject,
Input,
Renderer2,
} from '@angular/core';
import { FeatureConfigService } from '@spartacus/core';
import { FeatureConfigService, TranslationService } from '@spartacus/core';
import { take } from 'rxjs';

@Directive({
selector: '[cxNgSelectA11y]',
Expand All @@ -25,8 +27,20 @@ export class NgSelectA11yDirective implements AfterViewInit {
*/
@Input() cxNgSelectA11y: { ariaLabel?: string; ariaControls?: string };

protected translationService = inject(TranslationService);
private featureConfigService = inject(FeatureConfigService);

@HostListener('open')
onOpen() {
if (!this.featureConfigService?.isEnabled('a11yNgSelectOptionsCount')) {
return;
}
const observer = new MutationObserver((changes, observerInstance) =>
this.appendAriaLabelToOptions(changes, observerInstance)
);
observer.observe(this.elementRef.nativeElement, { childList: true });
}

constructor(
private renderer: Renderer2,
private elementRef: ElementRef
Expand Down Expand Up @@ -56,4 +70,26 @@ export class NgSelectA11yDirective implements AfterViewInit {
this.renderer.setAttribute(inputElement, 'aria-hidden', 'true');
}
}

appendAriaLabelToOptions(
_changes: MutationRecord[],
observerInstance: MutationObserver
) {
const options: HTMLOptionElement[] =
this.elementRef?.nativeElement.querySelectorAll('.ng-option');
if (options?.length) {
this.translationService
.translate('common.of')
.pipe(take(1))
.subscribe((translation) => {
options.forEach(
(option: HTMLOptionElement, index: string | number) => {
const ariaLabel = `${option.innerText}, ${+index + 1} ${translation} ${options.length}`;
this.renderer.setAttribute(option, 'aria-label', ariaLabel);
}
);
});
}
observerInstance.disconnect();
}
}

0 comments on commit c01711e

Please sign in to comment.