Skip to content

Commit e4feb68

Browse files
[ACS-9266][ACS-9234] Make notification and user menu accessible with keyboard (#10647)
* [ACS-9266][ACS-9234] Make notification and user menu accessible with keyboard * [ACS-9266] cr fix * [ACS-9266] cr fixes * [ACS-9266] fix eslint * [ACS-9266] fix circular dependency * [ACS-9266] cr fix
1 parent c8aa27a commit e4feb68

File tree

11 files changed

+263
-176
lines changed

11 files changed

+263
-176
lines changed

lib/core/src/lib/language-menu/language-menu.component.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { Component, EventEmitter, Output } from '@angular/core';
18+
import { Component, EventEmitter, Output, QueryList, ViewChildren } from '@angular/core';
1919
import { LanguageService } from './service/language.service';
2020
import { Observable } from 'rxjs';
2121
import { LanguageItem } from '../common/services/language-item.interface';
2222
import { CommonModule } from '@angular/common';
23-
import { MatMenuModule } from '@angular/material/menu';
23+
import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
2424

2525
@Component({
2626
selector: 'adf-language-menu',
@@ -37,6 +37,9 @@ export class LanguageMenuComponent {
3737
@Output()
3838
changedLanguage: EventEmitter<LanguageItem> = new EventEmitter<LanguageItem>();
3939

40+
@ViewChildren(MatMenuItem)
41+
menuItems: QueryList<MatMenuItem>;
42+
4043
languages$: Observable<LanguageItem[]>;
4144

4245
constructor(private languageService: LanguageService) {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*!
2+
* @license
3+
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { ComponentFixture, TestBed } from '@angular/core/testing';
19+
import { LanguagePickerComponent } from './language-picker.component';
20+
import { MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
21+
import { LanguageMenuComponent } from './language-menu.component';
22+
import { QueryList } from '@angular/core';
23+
import { CoreTestingModule, UnitTestingUtils } from '@alfresco/adf-core';
24+
25+
describe('LanguagePickerComponent', () => {
26+
let component: LanguagePickerComponent;
27+
let fixture: ComponentFixture<LanguagePickerComponent>;
28+
let testingUtils: UnitTestingUtils;
29+
30+
beforeEach(async () => {
31+
await TestBed.configureTestingModule({
32+
imports: [CoreTestingModule, LanguagePickerComponent]
33+
}).compileComponents();
34+
35+
fixture = TestBed.createComponent(LanguagePickerComponent);
36+
testingUtils = new UnitTestingUtils(fixture.debugElement);
37+
component = fixture.componentInstance;
38+
fixture.detectChanges();
39+
});
40+
41+
it('should assign menuItems to MatMenu in ngAfterViewInit', () => {
42+
testingUtils.getByDirective(MatMenuTrigger).nativeElement.click();
43+
fixture.detectChanges();
44+
const languageMenuComponent = testingUtils.getByDirective(LanguageMenuComponent).componentInstance;
45+
const menuItem1 = new MatMenuItem(null, null, null, null, null);
46+
const menuItem2 = new MatMenuItem(null, null, null, null, null);
47+
48+
languageMenuComponent.menuItems = new QueryList<MatMenuItem>();
49+
languageMenuComponent.menuItems.reset([menuItem1, menuItem2]);
50+
spyOn(component.menu, 'ngAfterContentInit').and.callThrough();
51+
52+
component.ngAfterViewInit();
53+
// eslint-disable-next-line no-underscore-dangle
54+
expect(component.menu._allItems.length).toBe(2);
55+
// eslint-disable-next-line no-underscore-dangle
56+
expect(component.menu._allItems.toArray()).toEqual([menuItem1, menuItem2]);
57+
expect(component.menu.ngAfterContentInit).toHaveBeenCalled();
58+
});
59+
});

lib/core/src/lib/language-menu/language-picker.component.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { Component, EventEmitter, Output } from '@angular/core';
18+
import { AfterViewInit, Component, EventEmitter, Output, QueryList, ViewChild } from '@angular/core';
1919
import { LanguageItem } from '../common/services/language-item.interface';
2020
import { CommonModule } from '@angular/common';
21-
import { MatMenuModule } from '@angular/material/menu';
21+
import { MatMenu, MatMenuItem, MatMenuModule } from '@angular/material/menu';
2222
import { TranslateModule } from '@ngx-translate/core';
2323
import { LanguageMenuComponent } from './language-menu.component';
2424
import { MatIconModule } from '@angular/material/icon';
@@ -37,7 +37,25 @@ import { MatIconModule } from '@angular/material/icon';
3737
</mat-menu>
3838
`
3939
})
40-
export class LanguagePickerComponent {
40+
export class LanguagePickerComponent implements AfterViewInit {
4141
@Output()
4242
public changedLanguage = new EventEmitter<LanguageItem>();
43+
44+
@ViewChild('langMenu')
45+
menu: MatMenu;
46+
47+
@ViewChild(MatMenuItem)
48+
menuItem: MatMenuItem;
49+
50+
@ViewChild(LanguageMenuComponent)
51+
languageMenuComponent: LanguageMenuComponent;
52+
53+
ngAfterViewInit() {
54+
const menuItems = this.languageMenuComponent.menuItems.filter((menuItem) => menuItem !== undefined);
55+
const menuItemsQueryList = new QueryList<MatMenuItem>();
56+
menuItemsQueryList.reset(menuItems);
57+
// eslint-disable-next-line no-underscore-dangle
58+
this.menu._allItems = menuItemsQueryList;
59+
this.menu.ngAfterContentInit();
60+
}
4361
}
Lines changed: 71 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,75 @@
1-
<div (keyup)="onKeyPress($event)" tabindex="-1" role="button" class="adf-notification-history-container">
2-
<button mat-button
3-
[matMenuTriggerFor]="menu"
4-
aria-hidden="false"
5-
[attr.aria-label]="'NOTIFICATIONS.OPEN_HISTORY' | translate"
6-
title="{{ 'NOTIFICATIONS.OPEN_HISTORY' | translate }}"
7-
class="adf-notification-history-menu_button"
8-
id="adf-notification-history-open-button"
9-
(menuOpened)="onMenuOpened()">
10-
<mat-icon matBadge="&#8288;"
11-
[matBadgeHidden]="!notifications.length"
12-
class="adf-notification-history-menu_button-icon"
13-
matBadgeColor="accent"
14-
matBadgeSize="small">notifications
15-
</mat-icon>
16-
</button>
17-
<mat-menu #menu="matMenu"
18-
[xPosition]="menuPositionX"
19-
[yPosition]="menuPositionY"
20-
id="adf-notification-history-menu"
21-
class="adf-notification-history-menu adf-notification-history-menu-panel">
22-
<div class="adf-notification-history-list"
23-
role="button"
24-
tabindex="0"
25-
(keyup.enter)="$event.stopPropagation()"
26-
(click)="$event.stopPropagation()">
27-
<div mat-subheader role="menuitem">
28-
<span class="adf-notification-history-menu-title">{{ 'NOTIFICATIONS.TITLE' | translate }}</span>
29-
<button *ngIf="notifications.length"
30-
id="adf-notification-history-mark-as-read"
31-
class="adf-notification-history-mark-as-read"
32-
mat-icon-button
33-
title="{{ 'NOTIFICATIONS.MARK_AS_READ' | translate }}"
34-
(click)="markAsRead()">
35-
<mat-icon>done_all</mat-icon>
36-
</button>
37-
</div>
1+
<button mat-button
2+
[matMenuTriggerFor]="menu"
3+
aria-hidden="false"
4+
[attr.aria-label]="'NOTIFICATIONS.OPEN_HISTORY' | translate"
5+
title="{{ 'NOTIFICATIONS.OPEN_HISTORY' | translate }}"
6+
class="adf-notification-history-menu_button"
7+
id="adf-notification-history-open-button"
8+
(menuOpened)="onMenuOpened()">
9+
<mat-icon matBadge="&#8288;"
10+
[matBadgeHidden]="!notifications.length"
11+
class="adf-notification-history-menu_button-icon"
12+
matBadgeColor="accent"
13+
matBadgeSize="small">notifications
14+
</mat-icon>
15+
</button>
3816

39-
<mat-divider />
17+
<mat-menu #menu="matMenu"
18+
[xPosition]="menuPositionX"
19+
[yPosition]="menuPositionY"
20+
id="adf-notification-history-menu"
21+
class="adf-notification-history-menu adf-notification-history-menu-panel">
22+
<div class="adf-notification-history-list-header">
23+
<span class="adf-notification-history-menu-title">{{ 'NOTIFICATIONS.TITLE' | translate }}</span>
24+
<button mat-menu-item
25+
*ngIf="notifications.length"
26+
id="adf-notification-history-mark-as-read"
27+
class="adf-notification-history-mark-as-read"
28+
title="{{ 'NOTIFICATIONS.MARK_AS_READ' | translate }}"
29+
(click)="markAsRead()">
30+
<mat-icon class="adf-notification-history-mark-as-read-icon">done_all</mat-icon>
31+
</button>
32+
</div>
4033

41-
<mat-list role="menuitem">
42-
<ng-container *ngIf="notifications.length; else empty_list_template">
43-
<mat-list-item *ngFor="let notification of paginatedNotifications"
44-
class="adf-notification-history-menu-item"
45-
(click)="onNotificationClick(notification)">
46-
<div *ngIf="notification.initiator; else no_avatar"
47-
matListItemAvatar
48-
[outerHTML]="notification.initiator | usernameInitials : 'adf-notification-initiator-pic'"></div>
49-
<ng-template #no_avatar>
50-
<mat-icon matListItemLine
51-
class="adf-notification-history-menu-initiator">{{notification.icon}}</mat-icon>
52-
</ng-template>
53-
<div class="adf-notification-history-menu-item-content">
54-
<p class="adf-notification-history-menu-text adf-notification-history-menu-message"
55-
*ngFor="let message of notification.messages"
56-
matListItemLine [title]="message">{{ message }}</p>
57-
<p class="adf-notification-history-menu-text adf-notification-history-menu-date"
58-
matListItemLine> {{notification.datetime | adfTimeAgo}} </p>
59-
</div>
60-
</mat-list-item>
61-
</ng-container>
62-
<ng-template #empty_list_template>
63-
<mat-list-item id="adf-notification-history-component-no-message"
64-
class="adf-notification-history-menu-no-message">
65-
<p class="adf-notification-history-menu-no-message-text" matListItemLine>{{ 'NOTIFICATIONS.NO_MESSAGE' | translate }}</p>
66-
</mat-list-item>
67-
</ng-template>
68-
</mat-list>
34+
<mat-divider/>
6935

70-
<mat-divider />
36+
<div class="adf-notification-history-item-list">
37+
<ng-container *ngIf="notifications.length; else empty_list_template">
38+
<button mat-menu-item
39+
*ngFor="let notification of paginatedNotifications"
40+
(click)="onNotificationClick(notification, $event)"
41+
class="adf-notification-history-menu-item">
42+
<div class="adf-notification-history-menu-item-content">
43+
<div *ngIf="notification.initiator; else no_avatar"
44+
[outerHTML]="notification.initiator | usernameInitials : 'adf-notification-initiator-pic'"></div>
45+
<ng-template #no_avatar>
46+
<mat-icon class="adf-notification-history-menu-initiator">
47+
{{ notification.icon }}
48+
</mat-icon>
49+
</ng-template>
50+
<div class="adf-notification-history-menu-item-content-message">
51+
<p class="adf-notification-history-menu-text adf-notification-history-menu-message"
52+
*ngFor="let message of notification.messages"
53+
[title]="message">{{ message }}</p>
54+
<p class="adf-notification-history-menu-text adf-notification-history-menu-date"
55+
> {{ notification.datetime | adfTimeAgo }} </p>
56+
</div>
57+
</div>
58+
</button>
59+
</ng-container>
60+
<ng-template #empty_list_template>
61+
<p mat-menu-item id="adf-notification-history-component-no-message"
62+
class="adf-notification-history-menu-no-message-text">
63+
{{ 'NOTIFICATIONS.NO_MESSAGE' | translate }}
64+
</p>
65+
</ng-template>
66+
</div>
7167

72-
<div class="adf-notification-history-load-more" role="menuitem" *ngIf="hasMoreNotifications()">
73-
<button mat-button (click)="loadMore()">
74-
{{ 'NOTIFICATIONS.LOAD_MORE' | translate }}
75-
</button>
76-
</div>
77-
</div>
78-
</mat-menu>
79-
</div>
68+
<mat-divider/>
69+
70+
<div class="adf-notification-history-load-more" *ngIf="hasMoreNotifications()">
71+
<button mat-menu-item (click)="loadMore($event)">
72+
{{ 'NOTIFICATIONS.LOAD_MORE' | translate }}
73+
</button>
74+
</div>
75+
</mat-menu>

0 commit comments

Comments
 (0)