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
7 changes: 6 additions & 1 deletion apps/docs/doc/breadcrumb/accessibility-doc.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AppDocSectionText } from '@/components/doc/app.docsectiontext';

@Component({
selector: 'accessibility-doc',
standalone: true,
imports: [AppDocSectionText],
imports: [RouterModule, AppDocSectionText],
template: ` <app-docsectiontext>
<h3>Screen Reader</h3>
<p>
Breadcrumb uses the <i>nav</i> element and since any attribute is passed to the root implicitly <i>aria-labelledby</i> or <i>aria-label</i> can be used to describe the component. Inside an ordered list is used where the list item
separators have <i>aria-hidden</i> to be able to ignored by the screen readers. If the last link represents the current route, <i>aria-current</i> is added with "page" as the value.
</p>
<p>
The home item renders an icon, so when it has no <i>label</i> the link is named with the <i>homeAriaLabel</i> property. When <i>homeAriaLabel</i> is not defined either, the <i>aria.home</i> key of the
<a routerLink="/configuration" fragment="locale">locale</a> configuration is used as the default.
</p>

<h3>Keyboard Support</h3>
<p>No special keyboard interaction is needed, all menuitems are focusable based on the page tab sequence.</p>
Expand Down
4 changes: 4 additions & 0 deletions apps/docs/doc/configuration/locale/apidoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ import { AppDocSectionText } from '@/components/doc/app.docsectiontext';
<td>aria.navigation</td>
<td>Navigation</td>
</tr>
<tr>
<td>aria.home</td>
<td>Home</td>
</tr>
<tr>
<td>aria.scrollTop</td>
<td>Scroll Top</td>
Expand Down
1 change: 1 addition & 0 deletions packages/optimus-ui/src/api/translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface Aria {
previous?: string;
next?: string;
navigation?: string;
home?: string;
scrollTop?: string;
moveTop?: string;
moveUp?: string;
Expand Down
54 changes: 54 additions & 0 deletions packages/optimus-ui/src/breadcrumb/breadcrumb.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { MenuItem } from '@openng/optimus-ui/api';
import { Optimus } from '@openng/optimus-ui/config';
import { BreadcrumbItemClickEvent } from '@openng/optimus-ui/types/breadcrumb';
import { Breadcrumb } from './breadcrumb';

Expand Down Expand Up @@ -828,6 +829,59 @@ describe('Breadcrumb', () => {
}
});

it('should label the icon only home link with the default aria.home translation', async () => {
component.home = { icon: 'pi pi-home' };
component.homeAriaLabel = undefined;
fixture.changeDetectorRef.markForCheck();

await fixture.whenStable();

fixture.detectChanges();

const homeLink = fixture.debugElement.query(By.css('[data-pc-section="homeitem"] a'));
expect(homeLink.nativeElement.getAttribute('aria-label')).toBe('Home');
});

it('should label the icon only home link with the configured aria.home translation', async () => {
TestBed.inject(Optimus).setTranslation({ aria: { home: 'Startseite' } });
component.home = { icon: 'pi pi-home' };
component.homeAriaLabel = undefined;
fixture.changeDetectorRef.markForCheck();

await fixture.whenStable();

fixture.detectChanges();

const homeLink = fixture.debugElement.query(By.css('[data-pc-section="homeitem"] a'));
expect(homeLink.nativeElement.getAttribute('aria-label')).toBe('Startseite');
});

it('should prefer homeAriaLabel over the aria.home translation', async () => {
component.home = { icon: 'pi pi-home' };
component.homeAriaLabel = 'Go to homepage';
fixture.changeDetectorRef.markForCheck();

await fixture.whenStable();

fixture.detectChanges();

const homeLink = fixture.debugElement.query(By.css('[data-pc-section="homeitem"] a'));
expect(homeLink.nativeElement.getAttribute('aria-label')).toBe('Go to homepage');
});

it('should not override a visible home label with the aria.home translation', async () => {
component.home = { icon: 'pi pi-home', label: 'Dashboard' };
component.homeAriaLabel = undefined;
fixture.changeDetectorRef.markForCheck();

await fixture.whenStable();

fixture.detectChanges();

const homeLink = fixture.debugElement.query(By.css('[data-pc-section="homeitem"] a'));
expect(homeLink.nativeElement.hasAttribute('aria-label')).toBe(false);
});

it('should handle tabindex for disabled items', async () => {
component.model = [{ label: 'Disabled Item', disabled: true }];
fixture.changeDetectorRef.markForCheck();
Expand Down
17 changes: 13 additions & 4 deletions packages/optimus-ui/src/breadcrumb/breadcrumb.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, ContentChild, ContentChildren, EventEmitter, inject, InjectionToken, Input, NgModule, Output, QueryList, TemplateRef, ViewEncapsulation } from '@angular/core';
import { Router, RouterLink, RouterLinkActive, RouterModule } from '@angular/router';
import { MenuItem, PrimeTemplate, SharedModule } from '@openng/optimus-ui/api';
import { MenuItem, PrimeTemplate, SharedModule, TranslationKeys } from '@openng/optimus-ui/api';
import { Badge } from '@openng/optimus-ui/badge';
import { BaseComponent, PARENT_INSTANCE } from '@openng/optimus-ui/basecomponent';
import { Bind } from '@openng/optimus-ui/bind';
Expand Down Expand Up @@ -30,7 +30,7 @@ const BREADCRUMB_INSTANCE = new InjectionToken<Breadcrumb>('BREADCRUMB_INSTANCE'
<a
[href]="home.url ? home.url : null"
*ngIf="!home.routerLink"
[attr.aria-label]="homeAriaLabel"
[attr.aria-label]="homeLinkAriaLabel"
[class]="cn(cx('itemLink'), home.linkClass)"
[ngStyle]="home.linkStyle"
(click)="onClick($event, home)"
Expand All @@ -52,7 +52,7 @@ const BREADCRUMB_INSTANCE = new InjectionToken<Breadcrumb>('BREADCRUMB_INSTANCE'
*ngIf="home.routerLink"
[routerLink]="home.routerLink"
routerLinkActive="p-menuitem-link-active"
[attr.aria-label]="homeAriaLabel"
[attr.aria-label]="homeLinkAriaLabel"
[queryParams]="home.queryParams"
[routerLinkActiveOptions]="home.routerLinkActiveOptions || { exact: false }"
[class]="cn(cx('itemLink'), home.linkClass)"
Expand Down Expand Up @@ -195,7 +195,7 @@ export class Breadcrumb extends BaseComponent<BreadcrumbPassThrough> {
*/
@Input() home: MenuItem | undefined;
/**
* Defines a string that labels the home icon for accessibility.
* Defines a string that labels the home icon for accessibility. Defaults to the `aria.home` translation when the home item has no visible label.
* @group Props
*/
@Input() homeAriaLabel: string | undefined;
Expand All @@ -210,6 +210,15 @@ export class Breadcrumb extends BaseComponent<BreadcrumbPassThrough> {

router = inject(Router);

get homeLinkAriaLabel(): string | undefined {
if (this.homeAriaLabel) {
return this.homeAriaLabel;
}

// A visible label already names the link, so an aria-label would only override it.
return this.home?.label ? undefined : this.config.getTranslation(TranslationKeys.ARIA)?.home;
}

onClick(event: MouseEvent, item: MenuItem) {
if (item.disabled) {
event.preventDefault();
Expand Down
1 change: 1 addition & 0 deletions packages/optimus-ui/src/config/optimus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class Optimus extends ThemeProvider {
previous: 'Previous',
next: 'Next',
navigation: 'Navigation',
home: 'Home',
scrollTop: 'Scroll Top',
moveTop: 'Move Top',
moveUp: 'Move Up',
Expand Down