mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +00:00
[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
This commit is contained in:
parent
c8aa27a87b
commit
e4feb68381
@ -15,12 +15,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, EventEmitter, Output } from '@angular/core';
|
import { Component, EventEmitter, Output, QueryList, ViewChildren } from '@angular/core';
|
||||||
import { LanguageService } from './service/language.service';
|
import { LanguageService } from './service/language.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { LanguageItem } from '../common/services/language-item.interface';
|
import { LanguageItem } from '../common/services/language-item.interface';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-language-menu',
|
selector: 'adf-language-menu',
|
||||||
@ -37,6 +37,9 @@ export class LanguageMenuComponent {
|
|||||||
@Output()
|
@Output()
|
||||||
changedLanguage: EventEmitter<LanguageItem> = new EventEmitter<LanguageItem>();
|
changedLanguage: EventEmitter<LanguageItem> = new EventEmitter<LanguageItem>();
|
||||||
|
|
||||||
|
@ViewChildren(MatMenuItem)
|
||||||
|
menuItems: QueryList<MatMenuItem>;
|
||||||
|
|
||||||
languages$: Observable<LanguageItem[]>;
|
languages$: Observable<LanguageItem[]>;
|
||||||
|
|
||||||
constructor(private languageService: LanguageService) {
|
constructor(private languageService: LanguageService) {
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { LanguagePickerComponent } from './language-picker.component';
|
||||||
|
import { MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
||||||
|
import { LanguageMenuComponent } from './language-menu.component';
|
||||||
|
import { QueryList } from '@angular/core';
|
||||||
|
import { CoreTestingModule, UnitTestingUtils } from '@alfresco/adf-core';
|
||||||
|
|
||||||
|
describe('LanguagePickerComponent', () => {
|
||||||
|
let component: LanguagePickerComponent;
|
||||||
|
let fixture: ComponentFixture<LanguagePickerComponent>;
|
||||||
|
let testingUtils: UnitTestingUtils;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CoreTestingModule, LanguagePickerComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LanguagePickerComponent);
|
||||||
|
testingUtils = new UnitTestingUtils(fixture.debugElement);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should assign menuItems to MatMenu in ngAfterViewInit', () => {
|
||||||
|
testingUtils.getByDirective(MatMenuTrigger).nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
const languageMenuComponent = testingUtils.getByDirective(LanguageMenuComponent).componentInstance;
|
||||||
|
const menuItem1 = new MatMenuItem(null, null, null, null, null);
|
||||||
|
const menuItem2 = new MatMenuItem(null, null, null, null, null);
|
||||||
|
|
||||||
|
languageMenuComponent.menuItems = new QueryList<MatMenuItem>();
|
||||||
|
languageMenuComponent.menuItems.reset([menuItem1, menuItem2]);
|
||||||
|
spyOn(component.menu, 'ngAfterContentInit').and.callThrough();
|
||||||
|
|
||||||
|
component.ngAfterViewInit();
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
expect(component.menu._allItems.length).toBe(2);
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
expect(component.menu._allItems.toArray()).toEqual([menuItem1, menuItem2]);
|
||||||
|
expect(component.menu.ngAfterContentInit).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@ -15,10 +15,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, EventEmitter, Output } from '@angular/core';
|
import { AfterViewInit, Component, EventEmitter, Output, QueryList, ViewChild } from '@angular/core';
|
||||||
import { LanguageItem } from '../common/services/language-item.interface';
|
import { LanguageItem } from '../common/services/language-item.interface';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenu, MatMenuItem, MatMenuModule } from '@angular/material/menu';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { LanguageMenuComponent } from './language-menu.component';
|
import { LanguageMenuComponent } from './language-menu.component';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
@ -37,7 +37,25 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
</mat-menu>
|
</mat-menu>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class LanguagePickerComponent {
|
export class LanguagePickerComponent implements AfterViewInit {
|
||||||
@Output()
|
@Output()
|
||||||
public changedLanguage = new EventEmitter<LanguageItem>();
|
public changedLanguage = new EventEmitter<LanguageItem>();
|
||||||
|
|
||||||
|
@ViewChild('langMenu')
|
||||||
|
menu: MatMenu;
|
||||||
|
|
||||||
|
@ViewChild(MatMenuItem)
|
||||||
|
menuItem: MatMenuItem;
|
||||||
|
|
||||||
|
@ViewChild(LanguageMenuComponent)
|
||||||
|
languageMenuComponent: LanguageMenuComponent;
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
const menuItems = this.languageMenuComponent.menuItems.filter((menuItem) => menuItem !== undefined);
|
||||||
|
const menuItemsQueryList = new QueryList<MatMenuItem>();
|
||||||
|
menuItemsQueryList.reset(menuItems);
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
this.menu._allItems = menuItemsQueryList;
|
||||||
|
this.menu.ngAfterContentInit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
<div (keyup)="onKeyPress($event)" tabindex="-1" role="button" class="adf-notification-history-container">
|
|
||||||
<button mat-button
|
<button mat-button
|
||||||
[matMenuTriggerFor]="menu"
|
[matMenuTriggerFor]="menu"
|
||||||
aria-hidden="false"
|
aria-hidden="false"
|
||||||
@ -14,66 +13,63 @@
|
|||||||
matBadgeSize="small">notifications
|
matBadgeSize="small">notifications
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<mat-menu #menu="matMenu"
|
<mat-menu #menu="matMenu"
|
||||||
[xPosition]="menuPositionX"
|
[xPosition]="menuPositionX"
|
||||||
[yPosition]="menuPositionY"
|
[yPosition]="menuPositionY"
|
||||||
id="adf-notification-history-menu"
|
id="adf-notification-history-menu"
|
||||||
class="adf-notification-history-menu adf-notification-history-menu-panel">
|
class="adf-notification-history-menu adf-notification-history-menu-panel">
|
||||||
<div class="adf-notification-history-list"
|
<div class="adf-notification-history-list-header">
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
(keyup.enter)="$event.stopPropagation()"
|
|
||||||
(click)="$event.stopPropagation()">
|
|
||||||
<div mat-subheader role="menuitem">
|
|
||||||
<span class="adf-notification-history-menu-title">{{ 'NOTIFICATIONS.TITLE' | translate }}</span>
|
<span class="adf-notification-history-menu-title">{{ 'NOTIFICATIONS.TITLE' | translate }}</span>
|
||||||
<button *ngIf="notifications.length"
|
<button mat-menu-item
|
||||||
|
*ngIf="notifications.length"
|
||||||
id="adf-notification-history-mark-as-read"
|
id="adf-notification-history-mark-as-read"
|
||||||
class="adf-notification-history-mark-as-read"
|
class="adf-notification-history-mark-as-read"
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'NOTIFICATIONS.MARK_AS_READ' | translate }}"
|
title="{{ 'NOTIFICATIONS.MARK_AS_READ' | translate }}"
|
||||||
(click)="markAsRead()">
|
(click)="markAsRead()">
|
||||||
<mat-icon>done_all</mat-icon>
|
<mat-icon class="adf-notification-history-mark-as-read-icon">done_all</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-divider/>
|
<mat-divider/>
|
||||||
|
|
||||||
<mat-list role="menuitem">
|
<div class="adf-notification-history-item-list">
|
||||||
<ng-container *ngIf="notifications.length; else empty_list_template">
|
<ng-container *ngIf="notifications.length; else empty_list_template">
|
||||||
<mat-list-item *ngFor="let notification of paginatedNotifications"
|
<button mat-menu-item
|
||||||
class="adf-notification-history-menu-item"
|
*ngFor="let notification of paginatedNotifications"
|
||||||
(click)="onNotificationClick(notification)">
|
(click)="onNotificationClick(notification, $event)"
|
||||||
|
class="adf-notification-history-menu-item">
|
||||||
|
<div class="adf-notification-history-menu-item-content">
|
||||||
<div *ngIf="notification.initiator; else no_avatar"
|
<div *ngIf="notification.initiator; else no_avatar"
|
||||||
matListItemAvatar
|
|
||||||
[outerHTML]="notification.initiator | usernameInitials : 'adf-notification-initiator-pic'"></div>
|
[outerHTML]="notification.initiator | usernameInitials : 'adf-notification-initiator-pic'"></div>
|
||||||
<ng-template #no_avatar>
|
<ng-template #no_avatar>
|
||||||
<mat-icon matListItemLine
|
<mat-icon class="adf-notification-history-menu-initiator">
|
||||||
class="adf-notification-history-menu-initiator">{{notification.icon}}</mat-icon>
|
{{ notification.icon }}
|
||||||
|
</mat-icon>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="adf-notification-history-menu-item-content">
|
<div class="adf-notification-history-menu-item-content-message">
|
||||||
<p class="adf-notification-history-menu-text adf-notification-history-menu-message"
|
<p class="adf-notification-history-menu-text adf-notification-history-menu-message"
|
||||||
*ngFor="let message of notification.messages"
|
*ngFor="let message of notification.messages"
|
||||||
matListItemLine [title]="message">{{ message }}</p>
|
[title]="message">{{ message }}</p>
|
||||||
<p class="adf-notification-history-menu-text adf-notification-history-menu-date"
|
<p class="adf-notification-history-menu-text adf-notification-history-menu-date"
|
||||||
matListItemLine> {{notification.datetime | adfTimeAgo}} </p>
|
> {{ notification.datetime | adfTimeAgo }} </p>
|
||||||
</div>
|
</div>
|
||||||
</mat-list-item>
|
</div>
|
||||||
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #empty_list_template>
|
<ng-template #empty_list_template>
|
||||||
<mat-list-item id="adf-notification-history-component-no-message"
|
<p mat-menu-item id="adf-notification-history-component-no-message"
|
||||||
class="adf-notification-history-menu-no-message">
|
class="adf-notification-history-menu-no-message-text">
|
||||||
<p class="adf-notification-history-menu-no-message-text" matListItemLine>{{ 'NOTIFICATIONS.NO_MESSAGE' | translate }}</p>
|
{{ 'NOTIFICATIONS.NO_MESSAGE' | translate }}
|
||||||
</mat-list-item>
|
</p>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-list>
|
</div>
|
||||||
|
|
||||||
<mat-divider/>
|
<mat-divider/>
|
||||||
|
|
||||||
<div class="adf-notification-history-load-more" role="menuitem" *ngIf="hasMoreNotifications()">
|
<div class="adf-notification-history-load-more" *ngIf="hasMoreNotifications()">
|
||||||
<button mat-button (click)="loadMore()">
|
<button mat-menu-item (click)="loadMore($event)">
|
||||||
{{ 'NOTIFICATIONS.LOAD_MORE' | translate }}
|
{{ 'NOTIFICATIONS.LOAD_MORE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
|
||||||
|
@ -3,37 +3,6 @@
|
|||||||
$notification-item-height: 72px;
|
$notification-item-height: 72px;
|
||||||
|
|
||||||
.adf {
|
.adf {
|
||||||
&-notification-history-container {
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-notification-history-list {
|
|
||||||
.adf-notification-history-menu-item-content-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stylelint-disable selector-class-pattern */
|
|
||||||
.mdc-list-item__secondary-text::before {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-notification-history-menu-item-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0 0 0 16px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 16px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
color: var(--theme-sidenav-user-menu-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-notification-history-menu-title {
|
&-notification-history-menu-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
@ -44,6 +13,7 @@ $notification-item-height: 72px;
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
margin-top: 1px;
|
||||||
|
|
||||||
.adf-notification-history-menu_button-icon {
|
.adf-notification-history-menu_button-icon {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
@ -54,18 +24,67 @@ $notification-item-height: 72px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-notification-history-list #{$mat-subheader} {
|
&-notification-history-menu {
|
||||||
|
.adf-notification-history-list-header {
|
||||||
|
padding: 10.5px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.adf-notification-history-mark-as-read {
|
||||||
|
display: flex;
|
||||||
|
padding: 10px;
|
||||||
|
width: auto;
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-notification-history-menu:has(.adf-notification-history-list) {
|
&-icon {
|
||||||
|
margin: 0;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-notification-history-menu-title {
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adf-notification-history-item-list {
|
||||||
|
padding-top: 8px;
|
||||||
|
|
||||||
.adf-notification-history-menu-item {
|
.adf-notification-history-menu-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: $notification-item-height;
|
height: $notification-item-height;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 16px;
|
display: block;
|
||||||
|
padding: 0 14px;
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 0 0 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: normal;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--theme-sidenav-user-menu-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.adf-notification-history-menu-item:focus {
|
.adf-notification-history-menu-item:focus {
|
||||||
@ -77,20 +96,29 @@ $notification-item-height: 72px;
|
|||||||
background-color: var(--adf-theme-background-hover-color);
|
background-color: var(--adf-theme-background-hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.adf-notification-history-menu-message:is(p),
|
.adf-notification-history-menu-message:is(p) {
|
||||||
.adf-notification-history-menu-no-message:is(p) {
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
font-size: var(--theme-body-1-font-size);
|
font-size: var(--theme-body-1-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.adf-notification-history-menu-no-message-text {
|
.adf-notification-history-item-list .adf-notification-history-menu-no-message-text {
|
||||||
font-size: 16px;
|
|
||||||
color: var(--theme-sidenav-user-menu-color);
|
color: var(--theme-sidenav-user-menu-color);
|
||||||
margin-bottom: 0;
|
margin: 0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
opacity: inherit;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 16px;
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.adf-notification-history-menu-date.adf-notification-history-menu-text:is(p) {
|
.adf-notification-history-menu-date.adf-notification-history-menu-text:is(p) {
|
||||||
font-size: var(--theme-caption-font-size);
|
font-size: var(--theme-caption-font-size);
|
||||||
|
text-indent: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.adf-notification-history-menu-initiator {
|
.adf-notification-history-menu-initiator {
|
||||||
@ -119,13 +147,11 @@ $notification-item-height: 72px;
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-height: 36px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-notification-history-mark-as-read {
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#{$mat-menu-panel}.adf-notification-history-menu.adf-notification-history-menu-panel {
|
#{$mat-menu-panel}.adf-notification-history-menu.adf-notification-history-menu-panel {
|
||||||
@ -134,18 +160,5 @@ $notification-item-height: 72px;
|
|||||||
|
|
||||||
#{$mat-menu-content} {
|
#{$mat-menu-content} {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
#{$mat-list} {
|
|
||||||
padding: 8px 0 0;
|
|
||||||
|
|
||||||
#{$mat-list-item-unscoped-content} {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
#{$mat-list-item-content} {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ describe('Notification History Component', () => {
|
|||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(overlayContainerElement.querySelector('#adf-notification-history-component-no-message')).toBeNull();
|
expect(overlayContainerElement.querySelector('#adf-notification-history-component-no-message')).toBeNull();
|
||||||
expect(overlayContainerElement.querySelector('.adf-notification-history-list').innerHTML).toContain('Example Message');
|
expect(overlayContainerElement.querySelector('.adf-notification-history-item-list').innerHTML).toContain('Example Message');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,17 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { AfterViewInit, ChangeDetectorRef, Component, DestroyRef, inject, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
AfterViewInit,
|
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
DestroyRef,
|
|
||||||
inject,
|
|
||||||
Input,
|
|
||||||
OnInit,
|
|
||||||
ViewChild,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core';
|
|
||||||
import { NotificationService } from '../services/notification.service';
|
import { NotificationService } from '../services/notification.service';
|
||||||
import { NOTIFICATION_TYPE, NotificationModel } from '../models/notification.model';
|
import { NOTIFICATION_TYPE, NotificationModel } from '../models/notification.model';
|
||||||
import { MatMenuModule, MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
|
import { MatMenuModule, MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
|
||||||
@ -121,22 +111,11 @@ export class NotificationHistoryComponent implements OnInit, AfterViewInit {
|
|||||||
this.createPagination();
|
this.createPagination();
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyPress(event: KeyboardEvent) {
|
|
||||||
this.closeUserModal(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private closeUserModal($event: KeyboardEvent) {
|
|
||||||
if ($event.keyCode === 27) {
|
|
||||||
this.trigger.closeMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
markAsRead() {
|
markAsRead() {
|
||||||
this.notifications = [];
|
this.notifications = [];
|
||||||
this.paginatedNotifications = [];
|
this.paginatedNotifications = [];
|
||||||
this.storageService.removeItem(NotificationHistoryComponent.NOTIFICATION_STORAGE);
|
this.storageService.removeItem(NotificationHistoryComponent.NOTIFICATION_STORAGE);
|
||||||
this.createPagination();
|
this.createPagination();
|
||||||
this.trigger.closeMenu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createPagination() {
|
createPagination() {
|
||||||
@ -149,7 +128,8 @@ export class NotificationHistoryComponent implements OnInit, AfterViewInit {
|
|||||||
this.paginatedNotifications = this.notifications.slice(0, this.pagination.skipCount);
|
this.paginatedNotifications = this.notifications.slice(0, this.pagination.skipCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMore() {
|
loadMore($event: MouseEvent) {
|
||||||
|
$event.stopPropagation();
|
||||||
this.pagination.skipCount = this.pagination.maxItems + this.pagination.skipCount;
|
this.pagination.skipCount = this.pagination.maxItems + this.pagination.skipCount;
|
||||||
this.pagination.hasMoreItems = this.notifications.length > this.pagination.skipCount;
|
this.pagination.hasMoreItems = this.notifications.length > this.pagination.skipCount;
|
||||||
this.paginatedNotifications = this.notifications.slice(0, this.pagination.skipCount);
|
this.paginatedNotifications = this.notifications.slice(0, this.pagination.skipCount);
|
||||||
@ -159,7 +139,8 @@ export class NotificationHistoryComponent implements OnInit, AfterViewInit {
|
|||||||
return this.pagination?.hasMoreItems;
|
return this.pagination?.hasMoreItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
onNotificationClick(notification: NotificationModel) {
|
onNotificationClick(notification: NotificationModel, $event: MouseEvent) {
|
||||||
|
$event.stopPropagation();
|
||||||
if (notification.clickCallBack) {
|
if (notification.clickCallBack) {
|
||||||
notification.clickCallBack(notification.args);
|
notification.clickCallBack(notification.args);
|
||||||
this.trigger.closeMenu();
|
this.trigger.closeMenu();
|
||||||
|
@ -87,7 +87,6 @@ $mat-calendar-table-header: '.mat-calendar-table-header';
|
|||||||
$mat-calendar-body-disabled: '.mat-calendar-body-disabled';
|
$mat-calendar-body-disabled: '.mat-calendar-body-disabled';
|
||||||
$mat-toolbar: '.mat-toolbar';
|
$mat-toolbar: '.mat-toolbar';
|
||||||
$mat-slide-toggle: '.mat-mdc-slide-toggle';
|
$mat-slide-toggle: '.mat-mdc-slide-toggle';
|
||||||
$mat-list: '.mat-mdc-list';
|
|
||||||
$mat-list-item-content: '.mdc-list-item__content';
|
$mat-list-item-content: '.mdc-list-item__content';
|
||||||
$mat-list-item-unscoped-content: '.mat-mdc-list-item-unscoped-content';
|
$mat-list-item-unscoped-content: '.mat-mdc-list-item-unscoped-content';
|
||||||
$mat-text-field-no-label: '.mdc-text-field--no-label';
|
$mat-text-field-no-label: '.mdc-text-field--no-label';
|
||||||
|
@ -19,10 +19,11 @@
|
|||||||
|
|
||||||
import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
|
import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { DynamicExtensionComponent } from './dynamic.component';
|
import { DynamicExtensionComponent } from './dynamic.component';
|
||||||
import { ComponentRegisterService } from '../../services/component-register.service';
|
import { ComponentRegisterService } from '../../services/component-register.service';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { MatMenuItem } from '@angular/material/menu';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'test-component',
|
selector: 'test-component',
|
||||||
@ -71,9 +72,10 @@ describe('DynamicExtensionComponent', () => {
|
|||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getInnerElement = () => fixture.debugElement.query(By.css('[data-automation-id="found-me"]'));
|
||||||
|
|
||||||
it('should load the TestComponent', () => {
|
it('should load the TestComponent', () => {
|
||||||
const innerElement = fixture.debugElement.query(By.css('[data-automation-id="found-me"]'));
|
expect(getInnerElement()).not.toBeNull();
|
||||||
expect(innerElement).not.toBeNull();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass through the data', () => {
|
it('should pass through the data', () => {
|
||||||
@ -90,6 +92,12 @@ describe('DynamicExtensionComponent', () => {
|
|||||||
const testComponent = fixture.debugElement.query(By.css('test-component')).componentInstance;
|
const testComponent = fixture.debugElement.query(By.css('test-component')).componentInstance;
|
||||||
expect(testComponent.data).toBe(data);
|
expect(testComponent.data).toBe(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should assign menuItem from dynamically generated component in ngAfterViewInit', () => {
|
||||||
|
getInnerElement().componentInstance.menuItem = new MatMenuItem(null, null, null, null, null);
|
||||||
|
component.ngAfterViewInit();
|
||||||
|
expect(component.menuItem).toBeInstanceOf(MatMenuItem);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Angular life-cycle methods in sub-component', () => {
|
describe('Angular life-cycle methods in sub-component', () => {
|
||||||
|
@ -15,9 +15,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input, ComponentRef, ViewChild, ViewContainerRef, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
|
import { Component, Input, ComponentRef, ViewChild, ViewContainerRef, OnDestroy, OnChanges, SimpleChanges, AfterViewInit } from '@angular/core';
|
||||||
import { ExtensionService } from '../../services/extension.service';
|
import { ExtensionService } from '../../services/extension.service';
|
||||||
import { ExtensionComponent } from '../../services/component-register.service';
|
import { ExtensionComponent } from '../../services/component-register.service';
|
||||||
|
import { MatMenuItem } from '@angular/material/menu';
|
||||||
|
|
||||||
// cSpell:words lifecycle
|
// cSpell:words lifecycle
|
||||||
@Component({
|
@Component({
|
||||||
@ -25,7 +26,7 @@ import { ExtensionComponent } from '../../services/component-register.service';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
template: `<div #content></div>`
|
template: `<div #content></div>`
|
||||||
})
|
})
|
||||||
export class DynamicExtensionComponent implements OnChanges, OnDestroy {
|
export class DynamicExtensionComponent implements OnChanges, OnDestroy, AfterViewInit {
|
||||||
@ViewChild('content', { read: ViewContainerRef, static: true })
|
@ViewChild('content', { read: ViewContainerRef, static: true })
|
||||||
content: ViewContainerRef;
|
content: ViewContainerRef;
|
||||||
|
|
||||||
@ -35,6 +36,9 @@ export class DynamicExtensionComponent implements OnChanges, OnDestroy {
|
|||||||
/** Data for the dynamically-loaded component instance. */
|
/** Data for the dynamically-loaded component instance. */
|
||||||
@Input() data: any;
|
@Input() data: any;
|
||||||
|
|
||||||
|
/** Provides the menu item of dynamically-loaded component instance. */
|
||||||
|
menuItem: MatMenuItem;
|
||||||
|
|
||||||
private componentRef: ComponentRef<ExtensionComponent>;
|
private componentRef: ComponentRef<ExtensionComponent>;
|
||||||
private loaded: boolean = false;
|
private loaded: boolean = false;
|
||||||
|
|
||||||
@ -61,6 +65,10 @@ export class DynamicExtensionComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.menuItem = this.componentRef?.instance?.menuItem;
|
||||||
|
}
|
||||||
|
|
||||||
private loadComponent() {
|
private loadComponent() {
|
||||||
const componentType = this.extensions.getComponentById<ExtensionComponent>(this.id);
|
const componentType = this.extensions.getComponentById<ExtensionComponent>(this.id);
|
||||||
if (componentType) {
|
if (componentType) {
|
||||||
|
@ -16,9 +16,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable, Type } from '@angular/core';
|
import { Injectable, Type } from '@angular/core';
|
||||||
|
import { MatMenuItem } from '@angular/material/menu';
|
||||||
|
|
||||||
export interface ExtensionComponent {
|
export interface ExtensionComponent {
|
||||||
data: any;
|
data: any;
|
||||||
|
menuItem?: MatMenuItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
|
Loading…
x
Reference in New Issue
Block a user