Migrate to new animations api (#12078)

This commit is contained in:
Denys Vuika
2026-07-23 12:21:10 +01:00
committed by GitHub
parent 9f21d602fc
commit 85ec35cdf9
62 changed files with 849 additions and 989 deletions
-1
View File
@@ -11,7 +11,6 @@
"url": "https://github.com/Alfresco/alfresco-ng2-components/issues"
},
"peerDependencies": {
"@angular/animations": ">=20.3.25",
"@angular/cdk": ">=20.2.14",
"@angular/common": ">=20.3.25",
"@angular/compiler": ">=20.3.25",
@@ -36,6 +36,7 @@ import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatTabGroupHarness } from '@angular/material/tabs/testing';
import { NoopAuthModule } from '@alfresco/adf-core';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
describe('ContentNodeSelectorComponent', () => {
let component: ContentNodeSelectorComponent;
@@ -61,6 +62,7 @@ describe('ContentNodeSelectorComponent', () => {
TestBed.configureTestingModule({
imports: [NoopAuthModule, ContentNodeSelectorComponent],
providers: [
provideNoopAnimations(),
{ provide: MAT_DIALOG_DATA, useValue: data },
{
provide: MatDialogRef,
@@ -127,7 +129,9 @@ describe('ContentNodeSelectorComponent', () => {
const tabGroup = await loader.getHarness(MatTabGroupHarness.with({ selector: '.adf-content-node-selector-dialog-content' }));
const tabToSelect = (await tabGroup.getTabs())[tabIndex];
return tabToSelect.select();
await tabToSelect.select();
fixture.detectChanges();
await fixture.whenStable();
};
describe('Data injecting with the "Material dialog way"', () => {
@@ -83,7 +83,7 @@ describe('LibraryFavoriteDirective', () => {
it('should call addFavorite() and display snackbar message on click event when selection is not a favorite', async () => {
spyOn(component.directive.favoritesApi, 'getFavoriteSite').and.returnValue(Promise.reject(new Error('error')));
spyOn(component.directive.favoritesApi, 'createFavorite').and.returnValue(Promise.resolve(null));
spyOn(component.directive.favoritesApi, 'createFavorite').and.returnValue(Promise.resolve({ entry: {} } as any));
spyOn(notificationService, 'showInfo');
fixture.detectChanges();
@@ -18,7 +18,9 @@
import {
AppConfigService,
AuthenticationService,
CustomEmptyContentTemplateDirective,
CustomLoadingContentTemplateDirective,
CustomNoPermissionTemplateDirective,
DataColumn,
DataColumnComponent,
DataColumnListComponent,
@@ -2009,7 +2011,7 @@ describe('DocumentList', () => {
});
@Component({
imports: [DocumentListComponent, CustomLoadingContentTemplateDirective],
imports: [DocumentListComponent, CustomLoadingContentTemplateDirective, CustomNoPermissionTemplateDirective, CustomEmptyContentTemplateDirective],
template: `
<adf-document-list #customDocumentList>
<adf-custom-loading-content-template>
@@ -114,7 +114,7 @@ describe('DocumentListService', () => {
}));
it('should use rootFolderId provided in options', () => {
const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.callThrough();
const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.returnValue(Promise.resolve(fakeFolder as any));
service.getFolder('/fake-root/fake-name', { rootFolderId: 'testRoot' }, ['isLocked']);
@@ -126,7 +126,7 @@ describe('DocumentListService', () => {
});
it('should use provided other values passed in options', () => {
const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.callThrough();
const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.returnValue(Promise.resolve(fakeFolder as any));
service.getFolder('/fake-root/fake-name', { rootFolderId: 'testRoot', maxItems: 10, skipCount: 5, where: 'where', orderBy: ['order'] }, [
'isLocked'
@@ -144,7 +144,7 @@ describe('DocumentListService', () => {
});
it('should add the includeTypes in the request Node Children if required', () => {
const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.callThrough();
const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.returnValue(Promise.resolve(fakeFolder as any));
service.getFolder('/fake-root/fake-name', {}, ['isLocked']);
@@ -156,7 +156,7 @@ describe('DocumentListService', () => {
});
it('should not add the includeTypes in the request Node Children if is duplicated', () => {
const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.callThrough();
const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.returnValue(Promise.resolve(fakeFolder as any));
service.getFolder('/fake-root/fake-name', {}, ['allowableOperations']);
@@ -168,7 +168,7 @@ describe('DocumentListService', () => {
});
it('should add the includeTypes in the request getFolderNode if required', () => {
const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.callThrough();
const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.returnValue(Promise.resolve({} as any));
service.getFolderNode('test-id', ['isLocked']);
@@ -179,7 +179,7 @@ describe('DocumentListService', () => {
});
it('should not add the includeTypes in the request getFolderNode if is duplicated', () => {
const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.callThrough();
const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.returnValue(Promise.resolve({} as any));
service.getFolderNode('test-id', ['allowableOperations']);
@@ -190,7 +190,7 @@ describe('DocumentListService', () => {
});
it('should add default includeTypes in the request getFolderNode if none is provided', () => {
const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.callThrough();
const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.returnValue(Promise.resolve({} as any));
service.getFolderNode('test-id');
@@ -19,6 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchPropertiesComponent } from './search-properties.component';
import { By } from '@angular/platform-browser';
import { MatOption } from '@angular/material/core';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
import { FileSizeUnit } from './file-size-unit.enum';
import { FileSizeOperator } from './file-size-operator.enum';
import { SearchProperties } from './search-properties';
@@ -60,7 +61,8 @@ describe('SearchPropertiesComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [SearchPropertiesComponent]
imports: [SearchPropertiesComponent],
providers: [provideNoopAnimations()]
});
fixture = TestBed.createComponent(SearchPropertiesComponent);
@@ -249,14 +251,16 @@ describe('SearchPropertiesComponent', () => {
expect(component.context.execute).toHaveBeenCalled();
});
it('should search by at most MB after selecting proper options', () => {
it('should search by at most MB after selecting proper options', async () => {
typeInFileSizeInput();
clickFileSizeOperatorsSelect();
getSelectOptions()[1].nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
clickFileSizeUnitsSelect();
getSelectOptions()[1].nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
component.submitValues();
expect(component.displayValue$.next).toHaveBeenCalledWith(
@@ -274,14 +278,16 @@ describe('SearchPropertiesComponent', () => {
expect(component.context.execute).toHaveBeenCalled();
});
it('should search by exactly GB after selecting proper options', () => {
it('should search by exactly GB after selecting proper options', async () => {
typeInFileSizeInput();
clickFileSizeOperatorsSelect();
getSelectOptions()[2].nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
clickFileSizeUnitsSelect();
getSelectOptions()[2].nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
component.submitValues();
expect(component.displayValue$.next).toHaveBeenCalledWith(
@@ -373,15 +379,17 @@ describe('SearchPropertiesComponent', () => {
});
});
it('should return correct value when inputs changed', () => {
it('should return correct value when inputs changed', async () => {
fixture.detectChanges();
typeInFileSizeInput();
clickFileSizeOperatorsSelect();
getSelectOptions()[1].nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
clickFileSizeUnitsSelect();
getSelectOptions()[1].nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
const extensions = [{ value: 'pdf' }, { value: 'txt' }];
getSearchChipAutocompleteInputComponent().optionsChanged.emit(extensions);
@@ -15,12 +15,13 @@
* limitations under the License.
*/
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { NoopTranslateModule } from '@alfresco/adf-core';
import { NgModule } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { BrowserTestingModule } from '@angular/platform-browser/testing';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
@NgModule({
imports: [BrowserDynamicTestingModule, NoopTranslateModule, NoopAnimationsModule]
imports: [BrowserTestingModule, NoopTranslateModule],
providers: [provideNoopAnimations()]
})
export class GlobalTestingModule {}
@@ -1,30 +1,35 @@
<div class="adf-new-version-container">
<adf-version-comparison *ngIf="showVersionComparison" [node]="node" [newFileVersion]="newFileVersion" />
<div class="adf-new-version-uploader-container" id="adf-new-version-uploader-container" [@uploadToggle]="uploadState">
<table class="adf-version-upload" *ngIf="uploadState !== 'close' && !versionList.isLoading">
<tr>
<td>
<adf-version-upload
id="adf-version-upload-button"
[node]="node"
[newFileVersion]="newFileVersion"
[currentVersion]="versionList?.latestVersion?.entry"
(success)="onUploadSuccess($event)"
(cancel)="onUploadCancel()"
(error)="onUploadError($event)" />
</td>
</tr>
</table>
@if (showVersionComparison) {
<adf-version-comparison [node]="node" [newFileVersion]="newFileVersion" />
}
<div class="adf-new-version-uploader-container" id="adf-new-version-uploader-container" [class.adf-upload-open]="uploadState === 'open'">
@if (uploadState !== 'close' && !versionList.isLoading) {
<div class="adf-version-upload">
<adf-version-upload
id="adf-version-upload-button"
[node]="node"
[newFileVersion]="newFileVersion"
[currentVersion]="versionList?.latestVersion?.entry"
(success)="onUploadSuccess($event)"
(cancel)="onUploadCancel()"
(error)="onUploadError($event)"
/>
</div>
}
</div>
<div class="adf-version-list-container">
<div class="adf-version-list-table">
<div>
<button mat-raised-button
@if (uploadState === 'close') {
<button
mat-raised-button
id="adf-show-version-upload-button"
class="adf-version-manager-upload-button"
(click)="toggleNewVersion()"
*ngIf="uploadState ==='close'">{{ 'ADF_VERSION_LIST.ACTIONS.UPLOAD.ADD' | translate }}
</button>
>
{{ 'ADF_VERSION_LIST.ACTIONS.UPLOAD.ADD' | translate }}
</button>
}
</div>
<div>
<adf-version-list
@@ -37,7 +42,8 @@
[allowVersionDelete]="allowVersionDelete"
(deleted)="refresh($event)"
(restored)="refresh($event)"
(viewVersion)="onViewVersion($event)" />
(viewVersion)="onViewVersion($event)"
/>
</div>
</div>
</div>
@@ -31,6 +31,18 @@ adf-version-manager {
height: 0;
float: left;
position: relative;
opacity: 0;
visibility: hidden;
transition:
height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
visibility 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
&.adf-upload-open {
height: 175px;
opacity: 1;
visibility: visible;
}
}
.adf-version-list.adf-version-list-element {
@@ -18,7 +18,6 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import { Node } from '@alfresco/js-api';
import { VersionListComponent } from './version-list.component';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { ContentService } from '../common/services/content.service';
import { NodesApiService } from '../common/services/nodes-api.service';
import { FileUploadErrorEvent } from '../common/events/file.event';
@@ -33,14 +32,6 @@ import { TranslatePipe } from '@ngx-translate/core';
imports: [CommonModule, VersionComparisonComponent, VersionUploadComponent, MatButtonModule, TranslatePipe, VersionListComponent],
templateUrl: './version-manager.component.html',
styleUrls: ['./version-manager.component.scss'],
animations: [
trigger('uploadToggle', [
state('open', style({ height: '175px', opacity: 1, visibility: 'visible' })),
state('close', style({ height: '0%', opacity: 0, visibility: 'hidden' })),
transition('open => close', [style({ visibility: 'hidden' }), animate('0.4s cubic-bezier(0.25, 0.8, 0.25, 1)')]),
transition('close => open', [style({ visibility: 'visible' }), animate('0.4s cubic-bezier(0.25, 0.8, 0.25, 1)')])
])
],
encapsulation: ViewEncapsulation.None
})
export class VersionManagerComponent implements OnInit {
@@ -33,7 +33,8 @@ import {
ViewerToolbarActionsComponent,
NoopAuthModule,
NoopTranslateModule,
UnitTestingUtils
UnitTestingUtils,
IconModule
} from '@alfresco/adf-core';
import { NodesApiService } from '../../common/services/nodes-api.service';
import { UploadService } from '../../common/services/upload.service';
@@ -66,7 +67,7 @@ class ViewerWithCustomToolbarComponent {}
@Component({
selector: 'adf-viewer-container-toolbar-actions',
imports: [MatIconModule, MatButtonModule, ViewerToolbarActionsComponent, AlfrescoViewerComponent],
imports: [MatIconModule, MatButtonModule, ViewerToolbarActionsComponent, AlfrescoViewerComponent, IconModule],
// eslint-disable-next-line @alfresco/eslint-angular/no-angular-material-selectors
template: `<adf-alfresco-viewer>
<adf-viewer-toolbar-actions>
@@ -100,7 +101,7 @@ class DummyDialogComponent {}
@Component({
selector: 'adf-viewer-container-open-with',
imports: [MatIconModule, MatMenuModule, ViewerOpenWithComponent, AlfrescoViewerComponent],
imports: [MatIconModule, MatMenuModule, ViewerOpenWithComponent, AlfrescoViewerComponent, IconModule],
// eslint-disable-next-line @alfresco/eslint-angular/no-angular-material-selectors
template: `
<adf-alfresco-viewer>
@@ -125,7 +126,7 @@ class ViewerWithCustomOpenWithComponent {}
@Component({
selector: 'adf-viewer-container-more-actions',
imports: [MatIconModule, MatMenuModule, ViewerMoreActionsComponent, AlfrescoViewerComponent],
imports: [MatIconModule, MatMenuModule, ViewerMoreActionsComponent, AlfrescoViewerComponent, IconModule],
// eslint-disable-next-line @alfresco/eslint-angular/no-angular-material-selectors
template: ` <adf-alfresco-viewer>
<adf-viewer-more-actions>
+2 -2
View File
@@ -18,10 +18,10 @@
import 'zone.js';
import 'zone.js/testing';
import { TestBed } from '@angular/core/testing';
import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { platformBrowserTesting } from '@angular/platform-browser/testing';
import { GlobalTestingModule } from './lib/testing/global-testing.module';
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserDynamicTesting(), {
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserTesting(), {
teardown: { destroyAfterEach: true }
});
@@ -292,7 +292,7 @@ describe('AdfHttpClient', () => {
req.flush(null, { status: 200, statusText: 'Ok' });
});
it('should correctly decode types to string', () => {
it('should correctly decode types to string', (done) => {
const options: RequestOptions = {
path: '',
httpMethod: 'POST',
@@ -301,7 +301,13 @@ describe('AdfHttpClient', () => {
}
};
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch((error) => fail(error));
angularHttpClient
.request('http://example.com', options, securityOptions, emitters)
.then((res) => {
expect(res).toEqual('');
done();
})
.catch((error) => done.fail(error));
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000%2B02%3A00');
@@ -16,6 +16,7 @@
*/
import { TestBed } from '@angular/core/testing';
import { lastValueFrom } from 'rxjs';
import { DummyFeaturesService } from './dummy-features.service';
describe('DummyFeaturesService', () => {
@@ -28,33 +29,29 @@ describe('DummyFeaturesService', () => {
service = TestBed.inject(DummyFeaturesService);
});
it('should initialize the service', () => {
service.init().subscribe((changeset) => {
expect(changeset).toBeUndefined();
});
it('should initialize the service', async () => {
const changeset = await lastValueFrom(service.init(), { defaultValue: undefined });
expect(changeset).toBeUndefined();
});
it('should return false when isOn$ is called', () => {
service.isOn$().subscribe((isOn) => {
expect(isOn).toBeFalse();
});
it('should return false when isOn$ is called', async () => {
const isOn = await lastValueFrom(service.isOn$(), { defaultValue: false });
expect(isOn).toBeFalse();
});
it('should return true when isOff$ is called with any key', () => {
service.isOff$('').subscribe((isOff) => {
expect(isOff).toBeTrue();
});
service.isOff$('key').subscribe((isOff) => {
expect(isOff).toBeTrue();
});
service.isOff$('salkjdaskd').subscribe((isOff) => {
expect(isOff).toBeTrue();
});
it('should return true when isOff$ is called with any key', async () => {
const isOff1 = await lastValueFrom(service.isOff$(''), { defaultValue: true });
expect(isOff1).toBeTrue();
const isOff2 = await lastValueFrom(service.isOff$('key'), { defaultValue: true });
expect(isOff2).toBeTrue();
const isOff3 = await lastValueFrom(service.isOff$('salkjdaskd'), { defaultValue: true });
expect(isOff3).toBeTrue();
});
it('should return an empty object when getFlags$ is called', () => {
service.getFlags$().subscribe((flags) => {
expect(flags).toEqual({});
});
it('should return an empty object when getFlags$ is called', async () => {
const flags = await lastValueFrom(service.getFlags$(), { defaultValue: {} });
expect(flags).toEqual({});
});
});
-1
View File
@@ -22,7 +22,6 @@
"date-fns": "^2.30.0"
},
"peerDependencies": {
"@angular/animations": ">=20.3.25",
"@angular/cdk": ">=20.2.14",
"@angular/common": ">=20.3.25",
"@angular/core": ">=20.3.25",
@@ -17,9 +17,8 @@
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { SessionTimeoutDialogComponent } from './session-timeout-dialog.component';
import { NoopTranslateModule } from '../../testing';
describe('SessionTimeoutDialogComponent', () => {
const dialogRef = { close: jasmine.createSpy('close') };
@@ -27,7 +26,7 @@ describe('SessionTimeoutDialogComponent', () => {
beforeEach(() => {
dialogRef.close.calls.reset();
TestBed.configureTestingModule({
imports: [SessionTimeoutDialogComponent, NoopAnimationsModule, TranslateModule.forRoot()],
imports: [SessionTimeoutDialogComponent, NoopTranslateModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: { dialogTimeoutMs: 3000 } }
@@ -1,40 +0,0 @@
/*!
* @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 { state, style, animate, transition, query, group, sequence, AnimationStateMetadata, AnimationTransitionMetadata } from '@angular/animations';
export const contextMenuAnimation: (AnimationStateMetadata | AnimationTransitionMetadata)[] = [
state(
'void',
style({
opacity: 0,
transform: 'scale(0.01, 0.01)'
})
),
transition(
'void => *',
sequence([
query('.mat-mdc-menu-content', style({ opacity: 0 })),
animate('100ms linear', style({ opacity: 1, transform: 'scale(1, 0.5)' })),
group([
query('.mat-mdc-menu-content', animate('400ms cubic-bezier(0.55, 0, 0.55, 0.2)', style({ opacity: 1 }))),
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({ transform: 'scale(1, 1)' }))
])
])
),
transition('* => void', animate('150ms 50ms linear', style({ opacity: 0 })))
];
@@ -1,15 +1,20 @@
<div mat-menu class="mat-mdc-menu-panel mdc-menu-surface mdc-menu-surface--open" @panelAnimation>
<div mat-menu class="mat-mdc-menu-panel mdc-menu-surface mdc-menu-surface--open adf-context-menu-animate">
<div id="adf-context-menu-content" class="mat-mdc-menu-content">
<ng-container *ngFor="let link of links">
<button *ngIf="link.model?.visible"
@for (link of links; track link) {
@if (link.model?.visible) {
<button
[attr.data-automation-id]="'context-' + (link.title || link.model?.title | translate)"
mat-menu-item
[disabled]="link.model?.disabled"
[title]="link.model?.tooltip | translate"
(click)="onMenuItemClick($event, link)">
<mat-icon *ngIf="link.model?.icon" [adf-icon]="link.model.icon" />
<span>{{ link.title || link.model?.title | translate }}</span>
</button>
</ng-container>
(click)="onMenuItemClick($event, link)"
>
@if (link.model?.icon) {
<mat-icon [adf-icon]="link.model.icon" />
}
<span>{{ link.title || link.model?.title | translate }}</span>
</button>
}
}
</div>
</div>
@@ -10,6 +10,48 @@
}
}
@keyframes menu-scale-in {
0% {
opacity: 0;
transform: scale(0.01, 0.01);
}
25% {
opacity: 1;
transform: scale(1, 0.5);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@keyframes menu-scale-out {
0% {
opacity: 1;
transform: scale(1, 1);
}
75% {
opacity: 0;
transform: scale(1, 0.5);
}
100% {
opacity: 0;
transform: scale(0.01, 0.01);
}
}
adf-context-menu {
animation: delayed-elevation 0.5s ease-in-out 0.1s forwards;
.adf-context-menu-animate {
animation: menu-scale-in 500ms cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
&.adf-closing {
animation: menu-scale-out 500ms cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
}
}
}
@@ -15,14 +15,11 @@
* limitations under the License.
*/
import { trigger } from '@angular/animations';
import { FocusKeyManager } from '@angular/cdk/a11y';
import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { ContextMenuOverlayRef } from './context-menu-overlay';
import { contextMenuAnimation } from './animations';
import { CONTEXT_MENU_DATA } from './context-menu.tokens';
import { AfterViewInit, Component, HostListener, QueryList, ViewChildren, ViewEncapsulation, inject } from '@angular/core';
import { NgForOf, NgIf } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
import { IconModule } from '../icon/icon.module';
@@ -37,16 +34,15 @@ import { ContextMenuItem } from './interfaces';
class: 'adf-context-menu'
},
encapsulation: ViewEncapsulation.None,
imports: [IconModule, MatMenuModule, NgForOf, NgIf, TranslatePipe],
animations: [trigger('panelAnimation', contextMenuAnimation)]
imports: [IconModule, MatMenuModule, TranslatePipe]
})
export class ContextMenuListComponent implements AfterViewInit {
private readonly contextMenuOverlayRef = inject<ContextMenuOverlayRef>(ContextMenuOverlayRef);
private readonly data = inject(CONTEXT_MENU_DATA, { optional: true });
private keyManager?: FocusKeyManager<MatMenuItem>;
private keyManager: FocusKeyManager<MatMenuItem>;
@ViewChildren(MatMenuItem) items: QueryList<MatMenuItem>;
links: ContextMenuItem[];
@ViewChildren(MatMenuItem) items = new QueryList<MatMenuItem>();
public readonly links: ContextMenuItem[] = inject(CONTEXT_MENU_DATA, { optional: true }) || [];
@HostListener('document:keydown.Escape', ['$event'])
handleKeydownEscape(event: Event) {
@@ -60,15 +56,11 @@ export class ContextMenuListComponent implements AfterViewInit {
if (event) {
const keyCode = event.keyCode;
if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
this.keyManager.onKeydown(event);
this.keyManager?.onKeydown(event);
}
}
}
constructor() {
this.links = this.data;
}
onMenuItemClick(event: Event, menuItem: ContextMenuItem) {
if (menuItem?.model?.disabled) {
event.preventDefault();
@@ -14,25 +14,24 @@
[cdkDropListSortPredicate]="filterDisabledColumns"
data-automation-id="datatable-row-header"
class="adf-datatable-row"
role="row">
role="row"
>
<!-- Drag -->
@if (enableDragRows) {
@if (enableDragRows) {
<div class="adf-datatable-cell-header adf-drag-column">
<span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.DRAG' | translate }}</span>
</div>
}
}
<!-- Actions (left) -->
@if (actions && actionsPosition === 'left') {
@if (actions && actionsPosition === 'left') {
<div class="adf-actions-column adf-datatable-cell-header">
<span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.ACTIONS' | translate }}</span>
</div>
}
}
<!-- Columns -->
@if (multiselect) {
@if (multiselect) {
<div class="adf-datatable-cell-header adf-datatable-checkbox">
<mat-checkbox
[indeterminate]="isSelectAllIndeterminate"
@@ -48,21 +47,28 @@
{{ 'ADF-DATATABLE.ACCESSIBILITY.SELECT_ALL' | translate }}
</mat-checkbox>
</div>
}
}
@for (col of getVisibleColumns(); track col.key; let lastColumn = $last; let columnIndex = $index) {
@for (col of getVisibleColumns(); track col.key; let lastColumn = $last; let columnIndex = $index) {
@if (col.title || !showProvidedActions) {
<div
class="adf-datatable-cell--{{col.type || 'text'}} {{col.cssClass}} adf-datatable-cell-header adf-datatable-cell-data"
class="adf-datatable-cell--{{ col.type || 'text' }} {{
col.cssClass
}} adf-datatable-cell-header adf-datatable-cell-data"
[attr.data-automation-id]="'auto_id_' + col.key"
[ngClass]="{
'adf-sortable': col.sortable,
'adf-datatable__cursor--pointer': !isResizing,
'adf-datatable__header--sorted-asc': isColumnSorted(col, 'asc'),
'adf-datatable__header--sorted-desc': isColumnSorted(col, 'desc')}"
[ngStyle]="(col.width) && !lastColumn && {'flex': getFlexValue(col)}"
'adf-datatable__header--sorted-desc': isColumnSorted(col, 'desc')
}"
[ngStyle]="col.width && !lastColumn && { flex: getFlexValue(col) }"
role="columnheader"
[attr.aria-label]="col.srTitle ? (col.srTitle | translate) : (col.title | translate) + (col.subtitle ? ' ' + (col.subtitle | translate) : '')"
[attr.aria-label]="
col.srTitle
? (col.srTitle | translate)
: (col.title | translate) + (col.subtitle ? ' ' + (col.subtitle | translate) : '')
"
[attr.aria-sort]="col.sortable ? (getAriaSort(col) | translate) : null"
cdkDrag
cdkDragLockAxis="x"
@@ -71,9 +77,10 @@
[cdkDragDisabled]="!col.draggable"
(mouseenter)="hoveredHeaderColumnIndex = columnIndex"
(mouseleave)="hoveredHeaderColumnIndex = -1"
adf-drop-zone dropTarget="header"
[dropColumn]="col">
adf-drop-zone
dropTarget="header"
[dropColumn]="col"
>
<div
adf-resizable
#resizableElement="adf-resizable"
@@ -86,9 +93,9 @@
col.srTitle
? (col.srTitle | translate)
: ('ADF-DATATABLE.ACCESSIBILITY.SORT_DEFAULT' | translate) +
' ' +
(col.title | translate) +
(col.subtitle ? ' ' + (col.subtitle | translate) : '')
' ' +
(col.title | translate) +
(col.subtitle ? ' ' + (col.subtitle | translate) : '')
"
(click)="onColumnHeaderClick(col, $event)"
(keyup.enter)="onColumnHeaderClick(col, $event)"
@@ -99,27 +106,24 @@
(resizeEnd)="onResizingEnd()"
[attr.data-automation-id]="'auto_header_content_id_' + col.key"
class="adf-datatable-cell-header-content"
[ngClass]="{ 'adf-datatable-cell-header-content--hovered':
hoveredHeaderColumnIndex === columnIndex &&
!isDraggingHeaderColumn &&
!isResizing && col.sortable}"
[ngClass]="{
'adf-datatable-cell-header-content--hovered':
hoveredHeaderColumnIndex === columnIndex && !isDraggingHeaderColumn && !isResizing && col.sortable
}"
>
@if (!col.header) {
@if (col.title) {
<span
title="{{col.title | translate}}"
class="adf-datatable-cell-value"
>
{{col.title | translate}}
<span title="{{ col.title | translate }}" class="adf-datatable-cell-value">
{{ col.title | translate }}
</span>
}
@if (col.subtitle) {
<span
title="{{col.subtitle | translate}}"
title="{{ col.subtitle | translate }}"
class="adf-datatable-cell-value adf-datatable-cell-header_subtitle"
>
({{col.subtitle | translate}})
({{ col.subtitle | translate }})
</span>
}
@@ -136,29 +140,27 @@
@if (col.header) {
<div class="adf-datatable-cell-value">
<ng-template [ngTemplateOutlet]="col.header" [ngTemplateOutletContext]="{$implicit: col}" />
<ng-template [ngTemplateOutlet]="col.header" [ngTemplateOutletContext]="{ $implicit: col }" />
</div>
}
<span
[class.adf-datatable__header--sorted-asc]="isColumnSorted(col, 'asc')"
[class.adf-datatable__header--sorted-desc]="isColumnSorted(col, 'desc')">
[class.adf-datatable__header--sorted-desc]="isColumnSorted(col, 'desc')"
>
</span>
@if (allowFiltering) {
<ng-template [ngTemplateOutlet]="headerFilterTemplate" [ngTemplateOutletContext]="{$implicit: col}" />
<ng-template [ngTemplateOutlet]="headerFilterTemplate" [ngTemplateOutletContext]="{ $implicit: col }" />
}
@if (col.draggable) {
<span
cdkDragHandle
[ngClass]="{ 'adf-datatable-cell-header-drag-icon': !isResizing }"
>
<span cdkDragHandle [ngClass]="{ 'adf-datatable-cell-header-drag-icon': !isResizing }">
@if (hoveredHeaderColumnIndex === columnIndex && !isResizing) {
<mat-icon
svgIcon="adf:drag_indicator"
class="adf-datatable-cell-header-drag-icon-visible"
[attr.data-automation-id]="'adf-datatable-cell-header-drag-icon-'+col.key"
[attr.data-automation-id]="'adf-datatable-cell-header-drag-icon-' + col.key"
aria-hidden="true"
/>
}
@@ -167,7 +169,11 @@
</div>
@if (isResizingEnabled && col.resizable && !lastColumn) {
<div
[ngClass]="hoveredHeaderColumnIndex === columnIndex && !isResizing || resizingColumnIndex === columnIndex ? 'adf-datatable__resize-handle-visible' : 'adf-datatable__resize-handle-hidden'"
[ngClass]="
(hoveredHeaderColumnIndex === columnIndex && !isResizing) || resizingColumnIndex === columnIndex
? 'adf-datatable__resize-handle-visible'
: 'adf-datatable__resize-handle-hidden'
"
adf-resize-handle
tabindex="0"
role="slider"
@@ -178,17 +184,18 @@
(click)="$event.stopPropagation()"
(keydown)="$event.stopPropagation()"
class="adf-datatable__resize-handle"
[resizableContainer]="resizableElement">
[resizableContainer]="resizableElement"
>
<div class="adf-datatable__resize-handle--divider"></div>
</div>
}
<div class="adf-drop-header-cell-placeholder" *cdkDragPlaceholder></div>
</div>
}
}
}
<!-- Header actions (right) -->
@if ((actions && actionsPosition === 'right') || (mainActionTemplate && showMainDatatableActions)) {
@if ((actions && actionsPosition === 'right') || (mainActionTemplate && showMainDatatableActions)) {
<div
class="adf-actions-column adf-datatable-actions-menu adf-datatable-cell-header adf-datatable__actions-cell"
[class.adf-datatable-actions-menu-provided]="showProvidedActions"
@@ -200,7 +207,8 @@
mat-icon-button
#mainMenuTrigger="matMenuTrigger"
(click)="onMainMenuOpen()"
[matMenuTriggerFor]="mainMenu">
[matMenuTriggerFor]="mainMenu"
>
<mat-icon adf-icon="view_week_outline" />
</button>
<mat-menu #mainMenu (closed)="onMainMenuClosed()">
@@ -209,13 +217,14 @@
[ngTemplateOutlet]="mainActionTemplate"
[ngTemplateOutletContext]="{
$implicit: mainMenuTrigger
}" />
}"
/>
</div>
</mat-menu>
<span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.ACTIONS' | translate }}</span>
}
</div>
}
}
</adf-datatable-row>
</div>
}
@@ -223,12 +232,18 @@
@if (!loading) {
<div
class="adf-datatable-body"
[ngClass]="{ 'adf-blur-datatable-body': blurOnResize && (isDraggingHeaderColumn || isResizing), 'adf-datatable-body__draggable': enableDragRows && !isDraggingRow, 'adf-datatable-body__dragging': isDraggingRow }"
[ngClass]="{
'adf-blur-datatable-body': blurOnResize && (isDraggingHeaderColumn || isResizing),
'adf-datatable-body__draggable': enableDragRows && !isDraggingRow,
'adf-datatable-body__dragging': isDraggingRow
}"
cdkDropList
[cdkDropListDisabled]="!enableDragRows"
role="rowgroup">
role="rowgroup"
>
@if (!noPermission) {
<adf-datatable-row *ngFor="let row of data.getRows(); let idx = index"
<adf-datatable-row
*ngFor="let row of data.getRows(); let idx = index"
cdkDrag
[cdkDragDisabled]="!enableDragRows"
(cdkDragDropped)="onDragDrop($event)"
@@ -255,29 +270,33 @@
>
<!-- Drag button -->
@if (enableDragRows) {
<div
role="gridcell"
class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-hover-only">
<div role="gridcell" class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-hover-only">
<mat-icon adf-icon="drag_indicator" aria-hidden="true" />
</div>
}
<!-- Actions (left) -->
@if (actions && actionsPosition === 'left') {
@if (actions && actionsPosition === 'left') {
<div role="gridcell" class="adf-datatable-cell">
<button mat-icon-button [matMenuTriggerFor]="menu" #actionsMenuTrigger="matMenuTrigger"
[ngClass]="getHideActionsWithoutHoverClass(actionsMenuTrigger)"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_left_' + idx"
[attr.data-automation-id]="'action_menu_' + idx">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
#actionsMenuTrigger="matMenuTrigger"
[ngClass]="getHideActionsWithoutHoverClass(actionsMenuTrigger)"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_left_' + idx"
[attr.data-automation-id]="'action_menu_' + idx"
>
<mat-icon adf-icon="more_vert" />
</button>
<mat-menu #menu="matMenu">
@for (action of getRowActions(row); track action.title) {
<button mat-menu-item
[attr.data-automation-id]="action.title"
[disabled]="action.disabled"
(click)="onExecuteRowAction(row, action)">
<button
mat-menu-item
[attr.data-automation-id]="action.title"
[disabled]="action.disabled"
(click)="onExecuteRowAction(row, action)"
>
@if (action.icon) {
<mat-icon [adf-icon]="action.icon" />
}
@@ -286,9 +305,9 @@
}
</mat-menu>
</div>
}
}
@if (multiselect) {
@if (multiselect) {
<label
(keydown.enter)="onEnterKeyPressed(row, $any($event))"
(click)="onCheckboxLabelClick(row, $event)"
@@ -316,24 +335,28 @@
<span class="adf-sr-only" aria-live="off">
{{ row.isSelected ? ('ADF-DATATABLE.ACCESSIBILITY.SELECTED' | translate) : '' }}
</span>
}
}
@for (col of getVisibleColumns(); track col.key; let lastColumn = $last;) {
@for (col of getVisibleColumns(); track col.key; let lastColumn = $last) {
<div
role="gridcell"
class="adf-datatable-cell adf-datatable-cell--{{col.type || 'text'}} {{col.cssClass}} adf-datatable-cell-data adf-datatable-cell--{{getAutomationValue(row)}}"
class="adf-datatable-cell adf-datatable-cell--{{ col.type || 'text' }} {{
col.cssClass
}} adf-datatable-cell-data adf-datatable-cell--{{ getAutomationValue(row) }}"
[attr.title]="col.title | translate"
[attr.data-automation-id]="getAutomationValue(row)"
[attr.aria-label]="col.title ? (col.title | translate) : null"
[attr.aria-hidden]='isRepresentationContent(col) ? "true" : null'
[attr.aria-hidden]="isRepresentationContent(col) ? 'true' : null"
(click)="onRowClick(row, $event)"
[attr.tabindex]="null"
(keydown.enter)="onEnterKeyPressed(row, $any($event))"
[adf-context-menu]="getContextMenuActions(row, col)"
[adf-context-menu-enabled]="contextMenu"
adf-drop-zone dropTarget="cell" [dropColumn]="col" [dropRow]="row"
[ngStyle]="(col.width) && !lastColumn && {'flex': getFlexValue(col)}"
adf-drop-zone
dropTarget="cell"
[dropColumn]="col"
[dropRow]="row"
[ngStyle]="col.width && !lastColumn && { flex: getFlexValue(col) }"
>
@if (!col.template) {
<div class="adf-datatable-cell-container">
@@ -342,7 +365,7 @@
<div class="adf-cell-value">
@if (isIconValue(row, col)) {
<mat-icon
[attr.aria-label]="col.srTitle? (col.srTitle | translate) : null"
[attr.aria-label]="col.srTitle ? (col.srTitle | translate) : null"
[attr.aria-hidden]="!asIconValue(row, col)"
[adf-icon]="asIconValue(row, col)"
/>
@@ -350,19 +373,35 @@
@if (row.isSelected && !multiselect) {
<mat-icon class="adf-datatable-selected" svgIcon="selected" />
} @else {
<img class="adf-datatable-center-img-ie"
[attr.aria-label]="(data.getValue(row, col) | fileType) === 'disable' ?
('ADF-DATATABLE.ACCESSIBILITY.ICON_DISABLED' | translate) :
'ADF-DATATABLE.ACCESSIBILITY.ICON_TEXT' | translate:{
type: 'ADF-DATATABLE.FILE_TYPE.' + (data.getValue(row, col) | fileType | uppercase) | translate
}"
[attr.alt]="(data.getValue(row, col) | fileType) === 'disable' ?
('ADF-DATATABLE.ACCESSIBILITY.ICON_DISABLED' | translate) :
'ADF-DATATABLE.ACCESSIBILITY.ICON_TEXT' | translate:{
type: 'ADF-DATATABLE.FILE_TYPE.' + (data.getValue(row, col) | fileType | uppercase) | translate
}"
<img
class="adf-datatable-center-img-ie"
[attr.aria-label]="
(data.getValue(row, col) | fileType) === 'disable'
? ('ADF-DATATABLE.ACCESSIBILITY.ICON_DISABLED' | translate)
: ('ADF-DATATABLE.ACCESSIBILITY.ICON_TEXT'
| translate
: {
type:
'ADF-DATATABLE.FILE_TYPE.' +
(data.getValue(row, col) | fileType | uppercase)
| translate
})
"
[attr.alt]="
(data.getValue(row, col) | fileType) === 'disable'
? ('ADF-DATATABLE.ACCESSIBILITY.ICON_DISABLED' | translate)
: ('ADF-DATATABLE.ACCESSIBILITY.ICON_TEXT'
| translate
: {
type:
'ADF-DATATABLE.FILE_TYPE.' +
(data.getValue(row, col) | fileType | uppercase)
| translate
})
"
src="{{ data.getValue(row, col) }}"
(error)="onImageLoadingError($event, row)">
(error)="onImageLoadingError($event, row)"
/>
}
}
</div>
@@ -381,59 +420,74 @@
@case ('date') {
<div
class="adf-cell-value adf-cell-date"
[attr.data-automation-id]="'date_' + (data.getValue(row, col, resolverFn) | adfLocalizedDate: 'medium') ">
<adf-date-cell class="adf-datatable-center-date-column-ie"
[attr.data-automation-id]="
'date_' + (data.getValue(row, col, resolverFn) | adfLocalizedDate: 'medium')
"
>
<adf-date-cell
class="adf-datatable-center-date-column-ie"
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)"
[dateConfig]="col.dateConfig" />
[dateConfig]="col.dateConfig"
/>
</div>
}
@case ('location') {
<div class="adf-cell-value"
[attr.data-automation-id]="'location' + data.getValue(row, col, resolverFn)">
<div
class="adf-cell-value"
[attr.data-automation-id]="'location' + data.getValue(row, col, resolverFn)"
>
<adf-location-cell
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)" />
[tooltip]="getCellTooltip(row, col)"
/>
</div>
}
@case ('fileSize') {
<div class="adf-cell-value"
[attr.data-automation-id]="'fileSize_' + data.getValue(row, col, resolverFn)">
<adf-filesize-cell class="adf-datatable-center-size-column-ie"
<div
class="adf-cell-value"
[attr.data-automation-id]="'fileSize_' + data.getValue(row, col, resolverFn)"
>
<adf-filesize-cell
class="adf-datatable-center-size-column-ie"
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)" />
[tooltip]="getCellTooltip(row, col)"
/>
</div>
}
@case ('text') {
<div class="adf-cell-value"
[attr.data-automation-id]="'text_' + data.getValue(row, col, resolverFn)">
<div class="adf-cell-value" [attr.data-automation-id]="'text_' + data.getValue(row, col, resolverFn)">
<adf-datatable-cell
[copyContent]="col.copyContent"
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)" />
[tooltip]="getCellTooltip(row, col)"
/>
</div>
}
@case ('boolean') {
<div class="adf-cell-value"
[attr.data-automation-id]="'boolean_' + data.getValue(row, col, resolverFn)">
<div
class="adf-cell-value"
[attr.data-automation-id]="'boolean_' + data.getValue(row, col, resolverFn)"
>
<adf-boolean-cell
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)" />
[tooltip]="getCellTooltip(row, col)"
/>
</div>
}
@case ('json') {
@@ -443,31 +497,36 @@
[data]="data"
[column]="col"
[resolverFn]="resolverFn"
[row]="row" />
[row]="row"
/>
</div>
}
@case ('amount') {
<div
class="adf-cell-value"
[attr.data-automation-id]="'amount_' + data.getValue(row, col, resolverFn)">
[attr.data-automation-id]="'amount_' + data.getValue(row, col, resolverFn)"
>
<adf-amount-cell
[data]="data"
[column]="col"
[resolverFn]="resolverFn"
[row]="row"
[currencyConfig]="col.currencyConfig" />
[currencyConfig]="col.currencyConfig"
/>
</div>
}
@case ('number') {
<div
class="adf-cell-value"
[attr.data-automation-id]="'number_' + data.getValue(row, col, resolverFn)">
[attr.data-automation-id]="'number_' + data.getValue(row, col, resolverFn)"
>
<adf-number-cell
[data]="data"
[column]="col"
[resolverFn]="resolverFn"
[row]="row"
[decimalConfig]="col.decimalConfig" />
[decimalConfig]="col.decimalConfig"
/>
</div>
}
@default {
@@ -482,7 +541,11 @@
<div class="adf-cell-value">
<ng-container
[ngTemplateOutlet]="col.template"
[ngTemplateOutletContext]="{ $implicit: { data: data, row: row, col: col }, value: data.getValue(row, col, resolverFn) }" />
[ngTemplateOutletContext]="{
$implicit: { data: data, row: row, col: col },
value: data.getValue(row, col, resolverFn)
}"
/>
</div>
</div>
}
@@ -490,28 +553,34 @@
}
<!-- Row actions (right) -->
@if (!showProvidedActions && ((actions && actionsPosition === 'right') || (mainActionTemplate && showMainDatatableActions))) {
@if (!showProvidedActions && ((actions && actionsPosition === 'right') || (mainActionTemplate && showMainDatatableActions))) {
<div
role="gridcell"
class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-center-actions-column-ie adf-datatable-actions-menu">
class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-center-actions-column-ie adf-datatable-actions-menu"
>
@if (actions && actionsPosition === 'right') {
<button mat-icon-button [matMenuTriggerFor]="menu" #actionsMenuTrigger="matMenuTrigger"
[ngClass]="getHideActionsWithoutHoverClass(actionsMenuTrigger)"
[attr.aria-label]="'ADF-DATATABLE.ACCESSIBILITY.ROW_OPTION_BUTTON' | translate"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_right_' + idx"
[attr.data-automation-id]="'action_menu_' + idx"
(keydown.enter)="actionsMenuTrigger.openMenu()">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
#actionsMenuTrigger="matMenuTrigger"
[ngClass]="getHideActionsWithoutHoverClass(actionsMenuTrigger)"
[attr.aria-label]="'ADF-DATATABLE.ACCESSIBILITY.ROW_OPTION_BUTTON' | translate"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_right_' + idx"
[attr.data-automation-id]="'action_menu_' + idx"
(keydown.enter)="actionsMenuTrigger.openMenu()"
>
<mat-icon adf-icon="more_vert" />
</button>
<mat-menu #menu="matMenu">
@for (action of getRowActions(row); track action.title) {
<button mat-menu-item
[attr.data-automation-id]="action.title"
[attr.aria-label]="action.title | translate"
[disabled]="action.disabled"
(click)="onExecuteRowAction(row, action)">
<button
mat-menu-item
[attr.data-automation-id]="action.title"
[attr.aria-label]="action.title | translate"
[disabled]="action.disabled"
(click)="onExecuteRowAction(row, action)"
>
@if (action.icon) {
<mat-icon [adf-icon]="action.icon" />
}
@@ -521,29 +590,23 @@
</mat-menu>
}
</div>
}
}
</adf-datatable-row>
@if (isEmpty()) {
<div role="row" class="adf-datatable-row">
<div class="adf-no-content-container adf-datatable-cell" role="gridcell">
@if (noContentTemplate) {
<ng-template
ngFor [ngForOf]="[data]"
[ngForTemplate]="noContentTemplate" />
<ng-template ngFor [ngForOf]="[data]" [ngForTemplate]="noContentTemplate" />
}
<ng-content select="adf-empty-list" />
</div>
</div>
}
} @else {
<div
role="row"
class="adf-datatable-row adf-no-permission__row">
<div role="row" class="adf-datatable-row adf-no-permission__row">
<div class="adf-no-permission__cell adf-no-content-container adf-datatable-cell">
@if (noPermissionTemplate) {
<ng-template
ngFor [ngForOf]="[data]"
[ngForTemplate]="noPermissionTemplate" />
<ng-template ngFor [ngForOf]="[data]" [ngForTemplate]="noPermissionTemplate" />
}
</div>
</div>
@@ -553,9 +616,7 @@
<div class="adf-datatable-row adf-datatable-data-loading">
<div class="adf-no-content-container adf-datatable-cell">
@if (loadingTemplate) {
<ng-template
ngFor [ngForOf]="[data]"
[ngForTemplate]="loadingTemplate" />
<ng-template ngFor [ngForOf]="[data]" [ngForTemplate]="loadingTemplate" />
}
</div>
</div>
@@ -59,7 +59,7 @@ import { ObjectDataTableAdapter } from '../../data/object-datatable-adapter';
import { DataCellEvent } from '../data-cell.event';
import { DataRowActionEvent } from '../data-row-action.event';
import { buffer, debounceTime, filter, map, share } from 'rxjs/operators';
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDragPlaceholder, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { ResizeEvent } from '../../directives/resizable/types';
@@ -122,7 +122,8 @@ export type ShowHeaderMode = (typeof ShowHeaderMode)[keyof typeof ShowHeaderMode
JsonCellComponent,
AmountCellComponent,
NumberCellComponent,
MatTooltipModule
MatTooltipModule,
CdkDragPlaceholder
],
templateUrl: './datatable.component.html',
styleUrls: ['./datatable.component.scss'],
@@ -1,27 +0,0 @@
/*!
* @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 { NgModule } from '@angular/core';
import { EditJsonDialogComponent } from './edit-json.dialog';
/* @deprecated Use EditJsonDialogComponent directly */
@NgModule({
declarations: [],
imports: [EditJsonDialogComponent],
exports: [EditJsonDialogComponent]
})
export class EditJsonDialogModule {}
-3
View File
@@ -16,13 +16,10 @@
*/
export * from './edit-json/edit-json.dialog';
export * from './edit-json/edit-json.dialog.module';
export * from './unsaved-changes-dialog/unsaved-changes-dialog.component';
export * from './unsaved-changes-dialog/unsaved-changes-dialog.module';
export * from './unsaved-changes-dialog/unsaved-changes.guard';
export * from './confirm-dialog/confirm.dialog';
export * from './confirm-dialog/confirm.dialog.module';
export * from './dialog';
@@ -1,26 +0,0 @@
/*!
* @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 { NgModule } from '@angular/core';
import { UnsavedChangesDialogComponent } from './unsaved-changes-dialog.component';
/** @deprecated import `UnsavedChangesDialogComponent` instead */
@NgModule({
imports: [UnsavedChangesDialogComponent],
exports: [UnsavedChangesDialogComponent]
})
export class UnsavedChangesDialogModule {}
@@ -1,4 +1,4 @@
<div @tooltip class="adf-tooltip-card" [style.width.px]="width">
<div class="adf-tooltip-card" [style.width.px]="width">
<img *ngIf="image " [src]="image" [width]="width" alt="{{text}}">
<hr *ngIf="image" />
<p *ngIf="text">{{text}}</p>
@@ -1,7 +1,18 @@
@use '@angular/material' as mat;
@keyframes tooltip-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
:host {
display: block;
animation: tooltip-fade-in 200ms ease-out;
}
div.adf-tooltip-card {
@@ -16,7 +16,6 @@
*/
import { Component, Input, SecurityContext, inject } from '@angular/core';
import { animate, style, transition, trigger } from '@angular/animations';
import { DomSanitizer } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
@@ -24,13 +23,7 @@ import { CommonModule } from '@angular/common';
selector: 'adf-tooltip-card-component',
imports: [CommonModule],
templateUrl: './tooltip-card.component.html',
styleUrls: ['./tooltip-card.component.scss'],
animations: [
trigger('tooltip', [
transition(':enter', [style({ opacity: 0 }), animate(200, style({ opacity: 1 }))]),
transition(':leave', [animate(200, style({ opacity: 0 }))])
])
]
styleUrls: ['./tooltip-card.component.scss']
})
export class TooltipCardComponent {
private readonly sanitizer = inject(DomSanitizer);
@@ -1,10 +1,14 @@
<div class="adf-error-container adf-error-widget-container">
<div *ngIf="error?.isActive()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ error.message | translate:translateParameters }}</div>
</div>
<div *ngIf="required" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ required }}</div>
</div>
@if (error?.isActive()) {
<div class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ error.message | translate: translateParameters }}</div>
</div>
}
@if (required) {
<div class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ required }}</div>
</div>
}
</div>
@@ -1,3 +1,15 @@
@keyframes adf-error-slide-in-down {
from {
opacity: 0;
transform: translateY(-100%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.adf-error {
display: flex;
align-items: center;
@@ -6,6 +18,10 @@
height: auto;
}
&-animate {
animation: adf-error-slide-in-down 300ms cubic-bezier(0.55, 0, 0.55, 0.2);
}
&-container {
padding-top: 0;
color: var(--mat-sys-error);
@@ -50,12 +50,6 @@ describe('ErrorWidgetComponent', () => {
expect(errorIcon).toEqual('error_outline');
});
it('should set subscriptAnimationState value', () => {
widget.ngOnChanges(errorChanges);
expect(widget.subscriptAnimationState).toEqual('enter');
});
it('should check proper error message', async () => {
widget.ngOnChanges(errorChanges);
@@ -17,8 +17,6 @@
/* eslint-disable @angular-eslint/component-selector */
import { animate, state, style, transition, trigger } from '@angular/animations';
import { NgIf } from '@angular/common';
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { TranslatePipe } from '@ngx-translate/core';
import { ErrorMessageModel } from '../core';
@@ -29,18 +27,6 @@ import { IconModule } from '../../../../icon/icon.module';
selector: 'error-widget',
templateUrl: './error.component.html',
styleUrls: ['./error.component.scss'],
animations: [
trigger('transitionMessages', [
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
transition('void => enter', [
style({
opacity: 0,
transform: 'translateY(-100%)'
}),
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
])
])
],
host: {
'(click)': 'event($event)',
'(blur)': 'event($event)',
@@ -52,7 +38,7 @@ import { IconModule } from '../../../../icon/icon.module';
'(invalid)': 'event($event)',
'(select)': 'event($event)'
},
imports: [NgIf, IconModule, TranslatePipe],
imports: [IconModule, TranslatePipe],
encapsulation: ViewEncapsulation.None
})
export class ErrorWidgetComponent extends WidgetComponent implements OnChanges {
@@ -63,18 +49,15 @@ export class ErrorWidgetComponent extends WidgetComponent implements OnChanges {
required: string;
translateParameters: any = null;
subscriptAnimationState: string = '';
ngOnChanges(changes: SimpleChanges) {
if (changes['required']) {
this.required = changes.required.currentValue;
this.subscriptAnimationState = 'enter';
}
if (changes['error']?.currentValue) {
if (changes.error.currentValue.isActive()) {
this.error = changes.error.currentValue;
this.translateParameters = this.error.getAttributesAsJsonObj();
this.subscriptAnimationState = 'enter';
}
}
}
@@ -1,16 +1,17 @@
<mat-sidenav-container class="adf-layout-container" autosize>
<mat-sidenav
class="adf-layout-container-sidenav"
[ngClass]="sidenavAnimationState?.value"
[position]="position"
[disableClose]="!isMobileScreenSize"
[@sidenavAnimation]="sidenavAnimationState"
[opened]="!isMobileScreenSize && !hideSidenav"
[mode]="isMobileScreenSize ? 'over' : 'side'">
[mode]="isMobileScreenSize ? 'over' : 'side'"
[style.width.px]="sidenavAnimationState?.params?.width">
<ng-content sidenav select="[app-layout-navigation]" />
</mat-sidenav>
<div>
<div class="adf-container-full-width" [@contentAnimationLeft]="getContentAnimationState()">
<div class="adf-container-full-width" [ngClass]="contentAnimationState?.value" [style.margin-left.px]="contentAnimationState?.params?.['margin-left']" [style.margin-right.px]="contentAnimationState?.params?.['margin-right']">
<ng-content select="[app-layout-content]" />
</div>
</div>
@@ -11,12 +11,16 @@ adf-layout-container {
border-right: 1px solid var(--mat-sys-outline-variant);
background: var(--mat-sys-surface);
color: var(--mat-sys-on-surface);
transition: width 400ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
}
.adf-container-full-width {
width: inherit;
overflow: hidden;
transition:
margin-left 400ms cubic-bezier(0.25, 0.8, 0.25, 1),
margin-right 400ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
/* query for Microsoft IE 11 */
@@ -19,40 +19,13 @@ import { Component, Input, ViewChild, OnInit, OnDestroy, ViewEncapsulation, OnCh
import { MatSidenav, MatSidenavModule } from '@angular/material/sidenav';
import { Direction } from '@angular/cdk/bidi';
import { CommonModule } from '@angular/common';
import { animate, state, style, transition, trigger } from '@angular/animations';
@Component({
selector: 'adf-layout-container',
imports: [CommonModule, MatSidenavModule],
templateUrl: './layout-container.component.html',
styleUrls: ['./layout-container.component.scss'],
encapsulation: ViewEncapsulation.None,
animations: [
trigger('sidenavAnimation', [
state('expanded', style({ width: '{{ width }}px' }), { params: { width: 0 } }),
state('compact', style({ width: '{{ width }}px' }), { params: { width: 0 } }),
transition('compact <=> expanded', animate('0.4s cubic-bezier(0.25, 0.8, 0.25, 1)'))
]),
trigger('contentAnimationLeft', [
state(
'expanded',
style({
'margin-left': '{{ margin-left }}px',
'margin-right': '{{ margin-right }}px'
}),
{ params: { 'margin-left': 0, 'margin-right': 0 } }
),
state(
'compact',
style({
'margin-left': '{{ margin-left }}px',
'margin-right': '{{ margin-right }}px'
}),
{ params: { 'margin-left': 0, 'margin-right': 0 } }
),
transition('expanded <=> compact', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))
])
]
encapsulation: ViewEncapsulation.None
})
export class LayoutContainerComponent implements OnInit, OnDestroy, OnChanges {
@Input() sidenavMin: number;
@@ -118,10 +91,6 @@ export class LayoutContainerComponent implements OnInit, OnDestroy, OnChanges {
return !!this.mediaQueryList?.matches;
}
getContentAnimationState(): any {
return this.contentAnimationState;
}
private get toggledSidenavAnimation(): any {
return this.sidenavAnimationState === this.SIDENAV_STATES.EXPANDED ? this.SIDENAV_STATES.COMPACT : this.SIDENAV_STATES.EXPANDED;
}
@@ -1,41 +0,0 @@
/*!
* @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 { trigger, transition, animate, style, state, AnimationTriggerMetadata } from '@angular/animations';
export const searchAnimation: AnimationTriggerMetadata = trigger('transitionMessages', [
state(
'active',
style({
'margin-left': '{{ margin-left }}px',
'margin-right': '{{ margin-right }}px',
transform: '{{ transform }}'
}),
{ params: { 'margin-left': 0, 'margin-right': 0, transform: 'translateX(0%)' } }
),
state(
'inactive',
style({
'margin-left': '{{ margin-left }}px',
'margin-right': '{{ margin-right }}px',
transform: '{{ transform }}'
}),
{ params: { 'margin-left': 0, 'margin-right': 0, transform: 'translateX(0%)' } }
),
state('no-animation', style({ transform: 'translateX(0%)', width: '100%' })),
transition('active <=> inactive', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))
]);
@@ -15,7 +15,6 @@
* limitations under the License.
*/
export * from './animations';
export * from './search-text-input.component';
export * from './search-trigger.directive';
export * from './search-text-input.module';
@@ -1,44 +1,52 @@
<div class="adf-search-container" [attr.state]="subscriptAnimationState.value">
<div class="adf-search-container-transition"
[@transitionMessages]="subscriptAnimationState"
(@transitionMessages.done)="applySearchFocus($event)">
<button mat-icon-button
*ngIf="expandable && !isSearchBarActive()"
<div class="adf-search-container-transition" [class.adf-search-active]="isSearchBarActive()" [ngStyle]="subscriptAnimationState.params">
@if (expandable && !isSearchBarActive()) {
<button
mat-icon-button
id="adf-search-button"
class="adf-search-button"
[ngClass]="{'adf-search-button-inactive': subscriptAnimationState.value === 'inactive'}"
[ngClass]="{ 'adf-search-button-inactive': subscriptAnimationState.value === 'inactive' }"
[title]="'CORE.SEARCH.BUTTON.TOOLTIP' | translate"
(click)="toggleSearchBar()"
(keyup.enter)="toggleSearchBar()">
<mat-icon [attr.aria-label]="'CORE.SEARCH.BUTTON.ARIA-LABEL' | translate" adf-icon="search" />
</button>
(keyup.enter)="toggleSearchBar()"
>
<mat-icon [attr.aria-label]="'CORE.SEARCH.BUTTON.ARIA-LABEL' | translate" adf-icon="search" />
</button>
}
<mat-form-field class="adf-input-form-field-divider" [hintLabel]="hintLabel">
<mat-label *ngIf='label'>{{label}}</mat-label>
<input matInput
#searchInput
[attr.aria-label]="'CORE.SEARCH.INPUT.ARIA-LABEL' | translate"
[attr.type]="inputType"
[autocomplete]="getAutoComplete()"
id="adf-control-input"
[(ngModel)]="searchTerm"
[placeholder]="placeholder"
(focus)="activateToolbar()"
(blur)="onBlur($event)"
(keyup.escape)="toggleSearchBar()"
(keyup.arrowdown)="selectFirstResult($event)"
(ngModelChange)="inputChange($event)"
[searchAutocomplete]="searchAutocomplete ? searchAutocomplete : null"
(keyup.enter)="searchSubmit($event)">
<button mat-icon-button
*ngIf="canShowClearSearch()"
@if (label) {
<mat-label>{{ label }}</mat-label>
}
<input
matInput
#searchInput
[attr.aria-label]="'CORE.SEARCH.INPUT.ARIA-LABEL' | translate"
[attr.type]="inputType"
[autocomplete]="getAutoComplete()"
id="adf-control-input"
[(ngModel)]="searchTerm"
[placeholder]="placeholder"
(focus)="activateToolbar()"
(blur)="onBlur($event)"
(keyup.escape)="toggleSearchBar()"
(keyup.arrowdown)="selectFirstResult($event)"
(ngModelChange)="inputChange($event)"
[searchAutocomplete]="searchAutocomplete ? searchAutocomplete : null"
(keyup.enter)="searchSubmit($event)"
/>
@if (canShowClearSearch()) {
<button
mat-icon-button
matSuffix
data-automation-id="adf-clear-search-button"
class="adf-clear-search-button"
[title]="'CORE.SEARCH.FILTER.BUTTONS.CLOSE' | translate"
(click)="resetSearch()"
(keyup.enter)="resetSearch()">
(keyup.enter)="resetSearch()"
>
<mat-icon adf-icon="close" />
</button>
}
</mat-form-field>
</div>
</div>
@@ -6,6 +6,10 @@
.adf-search-container-transition {
display: flex;
align-items: center;
transition:
transform 400ms cubic-bezier(0.25, 0.8, 0.25, 1),
margin-left 400ms cubic-bezier(0.25, 0.8, 0.25, 1),
margin-right 400ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
.adf {
@@ -164,7 +164,7 @@ describe('SearchTextInputComponent', () => {
function testMarginValue(isLtr: boolean): void {
userPreferencesService.setWithoutStore('textOrientation', isLtr ? 'ltr' : 'rtl');
clickSearchButton();
const expectedResult = isLtr ? { 'margin-left': 0 } : { 'margin-right': 0 };
const expectedResult = isLtr ? { 'margin-left': '0px' } : { 'margin-right': '0px' };
expect(component.subscriptAnimationState.params).toEqual(expectedResult);
discardPeriodicTasks();
}
@@ -16,7 +16,7 @@
*/
import { Direction } from '@angular/cdk/bidi';
import { NgClass, NgIf } from '@angular/common';
import { NgClass, NgStyle } from '@angular/common';
import {
Component,
DestroyRef,
@@ -24,8 +24,8 @@ import {
EventEmitter,
inject,
Input,
OnDestroy,
OnInit,
OnDestroy,
Output,
ViewChild,
ViewEncapsulation
@@ -35,10 +35,9 @@ import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { TranslatePipe } from '@ngx-translate/core';
import { Observable, Subject, Subscription } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { debounceTime, filter } from 'rxjs/operators';
import { UserPreferencesService } from '../common';
import { searchAnimation } from './animations';
import { SearchAnimationDirection, SearchAnimationState, SearchTextStateEnum } from './models/search-text-input.model';
import { SearchTriggerDirective } from './search-trigger.directive';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -48,9 +47,8 @@ import { IconModule } from '../icon/icon.module';
selector: 'adf-search-text-input',
templateUrl: './search-text-input.component.html',
styleUrls: ['./search-text-input.component.scss'],
animations: [searchAnimation],
encapsulation: ViewEncapsulation.None,
imports: [MatButtonModule, IconModule, TranslatePipe, MatFormFieldModule, MatInputModule, FormsModule, SearchTriggerDirective, NgIf, NgClass],
imports: [MatButtonModule, IconModule, TranslatePipe, MatFormFieldModule, MatInputModule, FormsModule, SearchTriggerDirective, NgClass, NgStyle],
host: {
class: 'adf-search-text-input'
}
@@ -91,7 +89,7 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
/** Listener for results-list events (focus, blur and focusout). */
@Input()
focusListener: Observable<FocusEvent>;
focusListener: Observable<FocusEvent> | null = null;
/** Collapse search bar on submit. */
@Input()
@@ -147,44 +145,44 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
/** Emitted when the search visibility changes. True when the search is active, false when it is inactive */
@Output()
searchVisibility: EventEmitter<boolean> = new EventEmitter<boolean>();
searchVisibility = new EventEmitter<boolean>();
@ViewChild('searchInput', { static: true })
searchInput: ElementRef;
subscriptAnimationState: any;
searchInput!: ElementRef;
animationStates: SearchAnimationDirection = {
ltr: {
active: { value: 'active', params: { 'margin-left': 13 } },
active: { value: 'active', params: { 'margin-left': '13px' } },
inactive: { value: 'inactive', params: { transform: 'translateX(100%)' } }
},
rtl: {
active: { value: 'active', params: { 'margin-right': 13 } },
active: { value: 'active', params: { 'margin-right': '13px' } },
inactive: { value: 'inactive', params: { transform: 'translateX(-100%)' } }
}
};
private dir = 'ltr';
private toggleSearch = new Subject<any>();
private focusSubscription: Subscription;
subscriptAnimationState: SearchAnimationState = this.animationStates.ltr.inactive;
private dir: keyof SearchAnimationDirection = 'ltr';
private readonly toggleSearch = new Subject<any>();
private readonly valueChange = new Subject<string>();
private readonly toggleSubscription: Subscription;
toggle$ = this.toggleSearch.asObservable();
private readonly destroyRef = inject(DestroyRef);
constructor() {
this.toggleSubscription = this.toggle$.pipe(debounceTime(200), takeUntilDestroyed()).subscribe(() => {
this.toggle$.pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
if (this.expandable) {
this.subscriptAnimationState = this.toggleAnimation();
if (this.subscriptAnimationState.value === 'inactive') {
this.searchTerm = '';
this.reset.emit(true);
if (document.activeElement.id === this.searchInput.nativeElement.id) {
if (document.activeElement?.id === this.searchInput.nativeElement.id) {
this.searchInput.nativeElement.blur();
}
} else if (this.subscriptAnimationState.value === 'active' && this.isDefaultStateCollapsed()) {
setTimeout(() => this.searchInput.nativeElement.focus(), 0);
}
this.emitVisibilitySearch();
}
@@ -205,10 +203,9 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
this.setupFocusEventHandlers();
}
applySearchFocus(animationDoneEvent) {
if (animationDoneEvent.toState === 'active' && this.isDefaultStateCollapsed()) {
this.searchInput.nativeElement.focus();
}
ngOnDestroy() {
this.toggleSearch.complete();
this.valueChange.complete();
}
getAutoComplete(): string {
@@ -218,23 +215,23 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
private toggleAnimation() {
if (this.dir === 'ltr') {
return this.subscriptAnimationState.value === 'inactive'
? { value: 'active', params: { 'margin-left': 0 } }
? { value: 'active', params: { 'margin-left': '0px' } }
: { value: 'inactive', params: { transform: 'translateX(100%)' } };
} else {
return this.subscriptAnimationState.value === 'inactive'
? { value: 'active', params: { 'margin-right': 0 } }
? { value: 'active', params: { 'margin-right': '0px' } }
: { value: 'inactive', params: { transform: 'translateX(-100%)' } };
}
}
private getDefaultState(dir: string): SearchAnimationState {
private getDefaultState(dir: keyof SearchAnimationDirection): SearchAnimationState {
if (this.dir) {
return this.getAnimationState(dir);
}
return this.animationStates.ltr.inactive;
}
private getAnimationState(dir: string): SearchAnimationState {
private getAnimationState(dir: keyof SearchAnimationDirection): SearchAnimationState {
if (this.expandable && this.isDefaultStateExpanded()) {
return this.animationStates[dir].active;
} else if (this.expandable) {
@@ -246,21 +243,21 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
private setupFocusEventHandlers() {
if (this.focusListener) {
const focusEvents: Observable<FocusEvent> = this.focusListener.pipe(
debounceTime(50),
filter(
($event: any) => this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout' || $event.type === 'focus')
),
takeUntilDestroyed(this.destroyRef)
);
this.focusSubscription = focusEvents.subscribe((event: FocusEvent) => {
if (event.type === 'focus') {
this.searchInput.nativeElement.focus();
} else {
this.toggleSearchBar();
}
});
this.focusListener
.pipe(
debounceTime(50),
filter(
($event: any) => this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout' || $event.type === 'focus')
),
takeUntilDestroyed(this.destroyRef)
)
.subscribe((event: FocusEvent) => {
if (event.type === 'focus') {
this.searchInput.nativeElement.focus();
} else {
this.toggleSearchBar();
}
});
}
}
@@ -270,11 +267,11 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
});
}
selectFirstResult($event) {
selectFirstResult($event: any) {
this.selectResult.emit($event);
}
onBlur($event) {
onBlur($event: any) {
if (this.collapseOnBlur && !$event.relatedTarget) {
this.resetSearch();
}
@@ -308,20 +305,6 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
return this.subscriptAnimationState.value === 'active';
}
ngOnDestroy() {
if (this.toggleSearch) {
this.toggleSubscription.unsubscribe();
this.toggleSearch.complete();
this.toggleSearch = null;
}
if (this.focusSubscription) {
this.focusSubscription.unsubscribe();
this.focusSubscription = null;
this.focusListener = null;
}
}
canShowClearSearch(): boolean {
return this.showClearButton && this.isSearchBarActive();
}
@@ -15,12 +15,13 @@
* limitations under the License.
*/
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { NoopTranslateModule } from './noop-translate.module';
import { NgModule } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { BrowserTestingModule } from '@angular/platform-browser/testing';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
@NgModule({
imports: [BrowserDynamicTestingModule, NoopTranslateModule, NoopAnimationsModule]
imports: [BrowserTestingModule, NoopTranslateModule],
providers: [provideNoopAnimations()]
})
export class GlobalTestingModule {}
+2 -2
View File
@@ -18,11 +18,11 @@
import 'zone.js';
import 'zone.js/testing';
import { TestBed } from '@angular/core/testing';
import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { platformBrowserTesting } from '@angular/platform-browser/testing';
import pdfjsLibraryMock from './src/lib/viewer/components/mock/pdfjs-lib.mock';
import { GlobalTestingModule } from './src/lib/testing/global-testing.module';
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserDynamicTesting(), {
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserTesting(), {
teardown: { destroyAfterEach: true }
});
@@ -20,7 +20,20 @@ import { PdfThumbListComponent } from './pdf-viewer-thumbnails.component';
import { UnitTestingUtils } from '@alfresco/adf-core';
import { DOWN_ARROW, ESCAPE, UP_ARROW } from '@angular/cdk/keycodes';
declare const pdfjsViewer: any;
class EventBusMock {
private readonly listeners = new Map<string, Array<(payload: any) => void>>();
on(eventName: string, callback: (payload: any) => void): void {
const handlers = this.listeners.get(eventName) ?? [];
handlers.push(callback);
this.listeners.set(eventName, handlers);
}
dispatch(eventName: string, payload: any): void {
const handlers = this.listeners.get(eventName) ?? [];
handlers.forEach((handler) => handler(payload));
}
}
describe('PdfThumbListComponent', () => {
let fixture: ComponentFixture<PdfThumbListComponent>;
@@ -67,7 +80,7 @@ describe('PdfThumbListComponent', () => {
page(15),
page(16)
],
eventBus: new pdfjsViewer.EventBus()
eventBus: new EventBusMock()
};
beforeEach(() => {
-1
View File
@@ -11,7 +11,6 @@
"url": "https://github.com/Alfresco/alfresco-ng2-components/issues"
},
"peerDependencies": {
"@angular/animations": ">=20.3.25",
"@angular/cdk": ">=20.2.14",
"@angular/common": ">=20.3.25",
"@angular/compiler": ">=20.3.25",
@@ -17,7 +17,7 @@
import { TestBed } from '@angular/core/testing';
import { FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN, FormCloudService } from './form-cloud.service';
import { BehaviorSubject, firstValueFrom, of } from 'rxjs';
import { BehaviorSubject, firstValueFrom, lastValueFrom, of } from 'rxjs';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { FORM_FIELD_VALIDATORS, FormFieldValidator, NoopAuthModule } from '@alfresco/adf-core';
import { HttpErrorResponse } from '@angular/common/http';
@@ -59,18 +59,16 @@ describe('Form Cloud service', () => {
});
describe('Form tests', () => {
it('should fetch and parse form', (done) => {
it('should fetch and parse form', async () => {
const formId = 'form-id';
requestSpy.and.returnValue(Promise.resolve(mockFormResponseBody));
service.getForm(appName, formId).subscribe((result) => {
expect(result).toBeDefined();
expect(result.formRepresentation.id).toBe(formId);
expect(result.formRepresentation.name).toBe('task-form');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET');
done();
});
const result = await lastValueFrom(service.getForm(appName, formId));
expect(result).toBeDefined();
expect(result.formRepresentation.id).toBe(formId);
expect(result.formRepresentation.name).toBe('task-form');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET');
});
it('should parse valid form json ', () => {
@@ -134,7 +132,7 @@ describe('Form Cloud service', () => {
expect(requestSpy.calls.all().some((call) => call.args[0].includes('/rb/'))).toBe(false);
});
it('should fetch task variables', (done) => {
it('should fetch task variables', async () => {
requestSpy.and.returnValue(
Promise.resolve({
list: {
@@ -172,18 +170,16 @@ describe('Form Cloud service', () => {
})
);
service.getTaskVariables(appName, taskId).subscribe((result) => {
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0].name).toBe('fakeProperty');
expect(result[0].value).toBe('fakeValue');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/query/v1/tasks/${taskId}/variables`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET');
done();
});
const result = await lastValueFrom(service.getTaskVariables(appName, taskId), { defaultValue: [] });
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0].name).toBe('fakeProperty');
expect(result[0].value).toBe('fakeValue');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/query/v1/tasks/${taskId}/variables`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET');
});
it('should fetch result if the variable value is 0', (done) => {
it('should fetch result if the variable value is 0', async () => {
requestSpy.and.returnValue(
Promise.resolve({
list: {
@@ -221,16 +217,14 @@ describe('Form Cloud service', () => {
})
);
service.getTaskVariables(appName, taskId).subscribe((result) => {
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0].name).toBe('fakeProperty');
expect(result[0].value).toBe(0);
done();
});
const result = await lastValueFrom(service.getTaskVariables(appName, taskId));
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0].name).toBe('fakeProperty');
expect(result[0].value).toBe(0);
});
it('should fetch task form flattened', (done) => {
it('should fetch task form flattened', async () => {
spyOn(service, 'getTask').and.returnValue(of(mockTaskResponseBody.entry));
spyOn(service, 'getForm').and.returnValue(
of({
@@ -241,41 +235,35 @@ describe('Form Cloud service', () => {
} as any)
);
service.getTaskForm(appName, taskId).subscribe((result) => {
expect(result).toBeDefined();
expect(result.name).toBe('task-form');
expect(result.taskId).toBe('id');
expect(result.taskName).toBe('name');
done();
});
const result = await lastValueFrom(service.getTaskForm(appName, taskId));
expect(result).toBeDefined();
expect(result.name).toBe('task-form');
expect(result.taskId).toBe('id');
expect(result.taskName).toBe('name');
});
it('should save task form', (done) => {
it('should save task form', async () => {
requestSpy.and.returnValue(Promise.resolve(mockTaskResponseBody));
const formId = 'form-id';
service.saveTaskForm(appName, taskId, processInstanceId, formId, {}).subscribe((result: any) => {
expect(result).toBeDefined();
expect(result.id).toBe('id');
expect(result.name).toBe('name');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}/save`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('POST');
done();
});
const result = await lastValueFrom(service.saveTaskForm(appName, taskId, processInstanceId, formId, {}));
expect(result).toBeDefined();
expect(result.id).toBe('id');
expect(result.name).toBe('name');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}/save`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('POST');
});
it('should complete task form', (done) => {
it('should complete task form', async () => {
requestSpy.and.returnValue(Promise.resolve(mockTaskResponseBody));
const formId = 'form-id';
service.completeTaskForm(appName, taskId, processInstanceId, formId, {}, '', 1).subscribe((result: any) => {
expect(result).toBeDefined();
expect(result.id).toBe('id');
expect(result.name).toBe('name');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}/submit/versions/1`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('POST');
done();
});
const result = await lastValueFrom(service.completeTaskForm(appName, taskId, processInstanceId, formId, {}, '', 1));
expect(result).toBeDefined();
expect(result.id).toBe('id');
expect(result.name).toBe('name');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}/submit/versions/1`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('POST');
});
});
});
@@ -56,29 +56,29 @@
<mat-progress-bar *ngIf="validationLoading" mode="indeterminate" />
<div class="adf-error-container adf-error-messages-container">
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }}</div>
</mat-error>
<mat-error *ngIf="searchGroupsControl.hasError('pattern')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchGroupsControl.hasError('pattern')" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}</div>
</mat-error>
<mat-error *ngIf="searchGroupsControl.hasError('maxlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchGroupsControl.hasError('maxlength')" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}</div>
</mat-error>
<mat-error *ngIf="searchGroupsControl.hasError('minlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchGroupsControl.hasError('minlength')" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</div>
</mat-error>
<mat-error *ngIf="(searchGroupsControl.hasError('required') || groupChipsCtrl.hasError('required')) && isDirty()"
[@transitionMessages]="subscriptAnimationState" class="adf-error">
class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </div>
</mat-error>
<mat-error *ngIf="searchGroupsControl.hasError('searchTypingError') && !this.isFocused"
data-automation-id="invalid-groups-typing-error" [@transitionMessages]="subscriptAnimationState" class="adf-error">
data-automation-id="invalid-groups-typing-error" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }}</div>
</mat-error>
@@ -45,6 +45,22 @@
}
}
@keyframes slide-down-fade-in {
from {
opacity: 0;
transform: translateY(-100%);
}
to {
opacity: 1;
transform: translateY(0%);
}
}
.adf-error-messages-container .adf-error-icon {
@include mixins.adf-error-icon;
}
.adf-error-messages-container .adf-error {
animation: slide-down-fade-in 300ms cubic-bezier(0.55, 0, 0.55, 0.2);
}
@@ -30,7 +30,6 @@ import {
ViewEncapsulation
} from '@angular/core';
import { ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, mergeMap, switchMap, tap } from 'rxjs/operators';
import { ComponentSelectionMode } from '../../types';
@@ -65,12 +64,6 @@ import { IconModule } from '@alfresco/adf-core';
],
templateUrl: './group-cloud.component.html',
styleUrls: ['./group-cloud.component.scss'],
animations: [
trigger('transitionMessages', [
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
transition('void => enter', [style({ opacity: 0, transform: 'translateY(-100%)' }), animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')])
])
],
encapsulation: ViewEncapsulation.None
})
export class GroupCloudComponent implements OnInit, OnChanges {
@@ -153,7 +146,6 @@ export class GroupCloudComponent implements OnInit, OnChanges {
invalidGroups: IdentityGroupModel[] = [];
searchGroups$ = new BehaviorSubject<IdentityGroupModel[]>(this.searchGroups);
subscriptAnimationState: string = 'enter';
isFocused: boolean;
touched: boolean = false;
@@ -68,21 +68,21 @@
<mat-progress-bar *ngIf="validationLoading" mode="indeterminate" />
<div class="adf-error-container adf-error-messages-container" *ngIf="showErrors">
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName: validateUsersMessage } }}</div>
</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('pattern')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchUserCtrl.hasError('pattern')" class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate : { pattern: getValidationPattern() } }}</div>
</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('maxlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchUserCtrl.hasError('maxlength')" class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate : { requiredLength: getValidationMaxLength() } }}
</div>
</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('minlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchUserCtrl.hasError('minlength')" class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate : { requiredLength: getValidationMinLength() } }}
@@ -90,17 +90,15 @@
</mat-error>
<mat-error
*ngIf="(searchUserCtrl.hasError('required') || userChipsCtrl.hasError('required')) && isDirty()"
[@transitionMessages]="subscriptAnimationState"
class="adf-error"
class="adf-error adf-error-animate"
>
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }}</div>
</mat-error>
<mat-error
*ngIf="searchUserCtrl.hasError('searchTypingError') && !this.isFocused"
[@transitionMessages]="subscriptAnimationState"
data-automation-id="invalid-users-typing-error"
class="adf-error"
class="adf-error adf-error-animate"
>
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName: searchedValue } }}</div>
@@ -1,5 +1,17 @@
@use '../../mixins' as mixins;
@keyframes adf-people-cloud-slide-in-down {
from {
opacity: 0;
transform: translateY(-100%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.adf {
&-people-cloud {
width: 100%;
@@ -49,6 +61,12 @@
}
}
.adf-error-messages-container .adf-error-icon {
@include mixins.adf-error-icon;
.adf-error-messages-container {
.adf-error-icon {
@include mixins.adf-error-icon;
}
.adf-error-animate {
animation: adf-people-cloud-slide-in-down 300ms cubic-bezier(0.55, 0, 0.55, 0.2);
}
}
@@ -34,7 +34,6 @@ import {
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, mergeMap, switchMap, tap } from 'rxjs/operators';
import { FullNamePipe, IconModule, InitialUsernamePipe } from '@alfresco/adf-core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { ComponentSelectionMode } from '../../types';
import { IdentityUserModel } from '../models/identity-user.model';
import { MatFormFieldAppearance, MatFormFieldModule, SubscriptSizing } from '@angular/material/form-field';
@@ -69,12 +68,6 @@ import { MatTooltipModule } from '@angular/material/tooltip';
providers: [FullNamePipe],
templateUrl: './people-cloud.component.html',
styleUrls: ['./people-cloud.component.scss'],
animations: [
trigger('transitionMessages', [
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
transition('void => enter', [style({ opacity: 0, transform: 'translateY(-100%)' }), animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')])
])
],
encapsulation: ViewEncapsulation.None
})
export class PeopleCloudComponent implements OnInit, OnChanges, AfterViewInit {
@@ -215,7 +208,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, AfterViewInit {
invalidUsers: IdentityUserModel[] = [];
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(this.searchUsers);
subscriptAnimationState: string = 'enter';
isFocused: boolean;
touched: boolean = false;
@@ -15,12 +15,13 @@
* limitations under the License.
*/
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { BrowserTestingModule } from '@angular/platform-browser/testing';
import { NoopTranslateModule } from '@alfresco/adf-core';
import { NgModule } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
@NgModule({
imports: [BrowserDynamicTestingModule, NoopTranslateModule, NoopAnimationsModule]
imports: [BrowserTestingModule, NoopTranslateModule],
providers: [provideNoopAnimations()]
})
export class GlobalTestingModule {}
+2 -2
View File
@@ -18,9 +18,9 @@
import 'zone.js';
import 'zone.js/testing';
import { TestBed } from '@angular/core/testing';
import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { platformBrowserTesting } from '@angular/platform-browser/testing';
import { GlobalTestingModule } from './lib/testing/global-testing.module';
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserDynamicTesting(), {
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserTesting(), {
teardown: { destroyAfterEach: true }
});
-1
View File
@@ -11,7 +11,6 @@
"url": "https://github.com/Alfresco/alfresco-ng2-components/issues"
},
"peerDependencies": {
"@angular/animations": ">=20.3.25",
"@angular/cdk": ">=20.2.14",
"@angular/common": ">=20.3.25",
"@angular/compiler": ">=20.3.25",
@@ -15,12 +15,13 @@
* limitations under the License.
*/
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { BrowserTestingModule } from '@angular/platform-browser/testing';
import { NoopTranslateModule } from '@alfresco/adf-core';
import { NgModule } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
@NgModule({
imports: [BrowserDynamicTestingModule, NoopTranslateModule, NoopAnimationsModule]
imports: [BrowserTestingModule, NoopTranslateModule],
providers: [provideNoopAnimations()]
})
export class GlobalTestingModule {}
+2 -2
View File
@@ -18,9 +18,9 @@
import 'zone.js';
import 'zone.js/testing';
import { TestBed } from '@angular/core/testing';
import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { platformBrowserTesting } from '@angular/platform-browser/testing';
import { GlobalTestingModule } from './lib/testing/global-testing.module';
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserDynamicTesting(), {
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserTesting(), {
teardown: { destroyAfterEach: true }
});
+1 -1
View File
@@ -1,5 +1,5 @@
import type { StorybookConfig } from '@storybook/angular';
import rootMain from '../../../.storybook/main';
import rootMain from '../../../.storybook/main.ts';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
+1 -1
View File
@@ -15,7 +15,7 @@
"includePaths": ["lib", "lib/core/src/lib"]
},
"styles": ["node_modules/cropperjs/dist/cropper.min.css", "node_modules/pdfjs-dist/web/pdf_viewer.css", "lib/stories/src/styles.scss"],
"sourceMap": true
"sourceMap": false
},
"configurations": {
"ci": {
+1 -1
View File
@@ -150,7 +150,7 @@
"ts-node": "10.9.2",
"typescript": "5.9.3",
"undici": "8.5.0",
"webpack": "5.107.2"
"webpack": "5.105.0"
},
"license": "Apache-2.0",
"engines": {
+224 -341
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -13,6 +13,7 @@ overrides:
ws: '>=8.20.1'
path-to-regexp: '>=0.1.13'
postcss: '>=8.5.10'
webpack: '5.105.0'
webpack-dev-server: '>=5.2.4'
uuid: '>=11.1.1'