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