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