Migrate to new animations api (#12078)

This commit is contained in:
Denys Vuika
2026-07-23 12:21:10 +01:00
committed by GitHub
parent 9f21d602fc
commit 85ec35cdf9
62 changed files with 849 additions and 989 deletions
@@ -17,9 +17,8 @@
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { SessionTimeoutDialogComponent } from './session-timeout-dialog.component';
import { NoopTranslateModule } from '../../testing';
describe('SessionTimeoutDialogComponent', () => {
const dialogRef = { close: jasmine.createSpy('close') };
@@ -27,7 +26,7 @@ describe('SessionTimeoutDialogComponent', () => {
beforeEach(() => {
dialogRef.close.calls.reset();
TestBed.configureTestingModule({
imports: [SessionTimeoutDialogComponent, NoopAnimationsModule, TranslateModule.forRoot()],
imports: [SessionTimeoutDialogComponent, NoopTranslateModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: { dialogTimeoutMs: 3000 } }
@@ -1,40 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { state, style, animate, transition, query, group, sequence, AnimationStateMetadata, AnimationTransitionMetadata } from '@angular/animations';
export const contextMenuAnimation: (AnimationStateMetadata | AnimationTransitionMetadata)[] = [
state(
'void',
style({
opacity: 0,
transform: 'scale(0.01, 0.01)'
})
),
transition(
'void => *',
sequence([
query('.mat-mdc-menu-content', style({ opacity: 0 })),
animate('100ms linear', style({ opacity: 1, transform: 'scale(1, 0.5)' })),
group([
query('.mat-mdc-menu-content', animate('400ms cubic-bezier(0.55, 0, 0.55, 0.2)', style({ opacity: 1 }))),
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({ transform: 'scale(1, 1)' }))
])
])
),
transition('* => void', animate('150ms 50ms linear', style({ opacity: 0 })))
];
@@ -1,15 +1,20 @@
<div mat-menu class="mat-mdc-menu-panel mdc-menu-surface mdc-menu-surface--open" @panelAnimation>
<div mat-menu class="mat-mdc-menu-panel mdc-menu-surface mdc-menu-surface--open adf-context-menu-animate">
<div id="adf-context-menu-content" class="mat-mdc-menu-content">
<ng-container *ngFor="let link of links">
<button *ngIf="link.model?.visible"
@for (link of links; track link) {
@if (link.model?.visible) {
<button
[attr.data-automation-id]="'context-' + (link.title || link.model?.title | translate)"
mat-menu-item
[disabled]="link.model?.disabled"
[title]="link.model?.tooltip | translate"
(click)="onMenuItemClick($event, link)">
<mat-icon *ngIf="link.model?.icon" [adf-icon]="link.model.icon" />
<span>{{ link.title || link.model?.title | translate }}</span>
</button>
</ng-container>
(click)="onMenuItemClick($event, link)"
>
@if (link.model?.icon) {
<mat-icon [adf-icon]="link.model.icon" />
}
<span>{{ link.title || link.model?.title | translate }}</span>
</button>
}
}
</div>
</div>
@@ -10,6 +10,48 @@
}
}
@keyframes menu-scale-in {
0% {
opacity: 0;
transform: scale(0.01, 0.01);
}
25% {
opacity: 1;
transform: scale(1, 0.5);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@keyframes menu-scale-out {
0% {
opacity: 1;
transform: scale(1, 1);
}
75% {
opacity: 0;
transform: scale(1, 0.5);
}
100% {
opacity: 0;
transform: scale(0.01, 0.01);
}
}
adf-context-menu {
animation: delayed-elevation 0.5s ease-in-out 0.1s forwards;
.adf-context-menu-animate {
animation: menu-scale-in 500ms cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
&.adf-closing {
animation: menu-scale-out 500ms cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
}
}
}
@@ -15,14 +15,11 @@
* limitations under the License.
*/
import { trigger } from '@angular/animations';
import { FocusKeyManager } from '@angular/cdk/a11y';
import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { ContextMenuOverlayRef } from './context-menu-overlay';
import { contextMenuAnimation } from './animations';
import { CONTEXT_MENU_DATA } from './context-menu.tokens';
import { AfterViewInit, Component, HostListener, QueryList, ViewChildren, ViewEncapsulation, inject } from '@angular/core';
import { NgForOf, NgIf } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
import { IconModule } from '../icon/icon.module';
@@ -37,16 +34,15 @@ import { ContextMenuItem } from './interfaces';
class: 'adf-context-menu'
},
encapsulation: ViewEncapsulation.None,
imports: [IconModule, MatMenuModule, NgForOf, NgIf, TranslatePipe],
animations: [trigger('panelAnimation', contextMenuAnimation)]
imports: [IconModule, MatMenuModule, TranslatePipe]
})
export class ContextMenuListComponent implements AfterViewInit {
private readonly contextMenuOverlayRef = inject<ContextMenuOverlayRef>(ContextMenuOverlayRef);
private readonly data = inject(CONTEXT_MENU_DATA, { optional: true });
private keyManager?: FocusKeyManager<MatMenuItem>;
private keyManager: FocusKeyManager<MatMenuItem>;
@ViewChildren(MatMenuItem) items: QueryList<MatMenuItem>;
links: ContextMenuItem[];
@ViewChildren(MatMenuItem) items = new QueryList<MatMenuItem>();
public readonly links: ContextMenuItem[] = inject(CONTEXT_MENU_DATA, { optional: true }) || [];
@HostListener('document:keydown.Escape', ['$event'])
handleKeydownEscape(event: Event) {
@@ -60,15 +56,11 @@ export class ContextMenuListComponent implements AfterViewInit {
if (event) {
const keyCode = event.keyCode;
if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
this.keyManager.onKeydown(event);
this.keyManager?.onKeydown(event);
}
}
}
constructor() {
this.links = this.data;
}
onMenuItemClick(event: Event, menuItem: ContextMenuItem) {
if (menuItem?.model?.disabled) {
event.preventDefault();
@@ -14,25 +14,24 @@
[cdkDropListSortPredicate]="filterDisabledColumns"
data-automation-id="datatable-row-header"
class="adf-datatable-row"
role="row">
role="row"
>
<!-- Drag -->
@if (enableDragRows) {
@if (enableDragRows) {
<div class="adf-datatable-cell-header adf-drag-column">
<span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.DRAG' | translate }}</span>
</div>
}
}
<!-- Actions (left) -->
@if (actions && actionsPosition === 'left') {
@if (actions && actionsPosition === 'left') {
<div class="adf-actions-column adf-datatable-cell-header">
<span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.ACTIONS' | translate }}</span>
</div>
}
}
<!-- Columns -->
@if (multiselect) {
@if (multiselect) {
<div class="adf-datatable-cell-header adf-datatable-checkbox">
<mat-checkbox
[indeterminate]="isSelectAllIndeterminate"
@@ -48,21 +47,28 @@
{{ 'ADF-DATATABLE.ACCESSIBILITY.SELECT_ALL' | translate }}
</mat-checkbox>
</div>
}
}
@for (col of getVisibleColumns(); track col.key; let lastColumn = $last; let columnIndex = $index) {
@for (col of getVisibleColumns(); track col.key; let lastColumn = $last; let columnIndex = $index) {
@if (col.title || !showProvidedActions) {
<div
class="adf-datatable-cell--{{col.type || 'text'}} {{col.cssClass}} adf-datatable-cell-header adf-datatable-cell-data"
class="adf-datatable-cell--{{ col.type || 'text' }} {{
col.cssClass
}} adf-datatable-cell-header adf-datatable-cell-data"
[attr.data-automation-id]="'auto_id_' + col.key"
[ngClass]="{
'adf-sortable': col.sortable,
'adf-datatable__cursor--pointer': !isResizing,
'adf-datatable__header--sorted-asc': isColumnSorted(col, 'asc'),
'adf-datatable__header--sorted-desc': isColumnSorted(col, 'desc')}"
[ngStyle]="(col.width) && !lastColumn && {'flex': getFlexValue(col)}"
'adf-datatable__header--sorted-desc': isColumnSorted(col, 'desc')
}"
[ngStyle]="col.width && !lastColumn && { flex: getFlexValue(col) }"
role="columnheader"
[attr.aria-label]="col.srTitle ? (col.srTitle | translate) : (col.title | translate) + (col.subtitle ? ' ' + (col.subtitle | translate) : '')"
[attr.aria-label]="
col.srTitle
? (col.srTitle | translate)
: (col.title | translate) + (col.subtitle ? ' ' + (col.subtitle | translate) : '')
"
[attr.aria-sort]="col.sortable ? (getAriaSort(col) | translate) : null"
cdkDrag
cdkDragLockAxis="x"
@@ -71,9 +77,10 @@
[cdkDragDisabled]="!col.draggable"
(mouseenter)="hoveredHeaderColumnIndex = columnIndex"
(mouseleave)="hoveredHeaderColumnIndex = -1"
adf-drop-zone dropTarget="header"
[dropColumn]="col">
adf-drop-zone
dropTarget="header"
[dropColumn]="col"
>
<div
adf-resizable
#resizableElement="adf-resizable"
@@ -86,9 +93,9 @@
col.srTitle
? (col.srTitle | translate)
: ('ADF-DATATABLE.ACCESSIBILITY.SORT_DEFAULT' | translate) +
' ' +
(col.title | translate) +
(col.subtitle ? ' ' + (col.subtitle | translate) : '')
' ' +
(col.title | translate) +
(col.subtitle ? ' ' + (col.subtitle | translate) : '')
"
(click)="onColumnHeaderClick(col, $event)"
(keyup.enter)="onColumnHeaderClick(col, $event)"
@@ -99,27 +106,24 @@
(resizeEnd)="onResizingEnd()"
[attr.data-automation-id]="'auto_header_content_id_' + col.key"
class="adf-datatable-cell-header-content"
[ngClass]="{ 'adf-datatable-cell-header-content--hovered':
hoveredHeaderColumnIndex === columnIndex &&
!isDraggingHeaderColumn &&
!isResizing && col.sortable}"
[ngClass]="{
'adf-datatable-cell-header-content--hovered':
hoveredHeaderColumnIndex === columnIndex && !isDraggingHeaderColumn && !isResizing && col.sortable
}"
>
@if (!col.header) {
@if (col.title) {
<span
title="{{col.title | translate}}"
class="adf-datatable-cell-value"
>
{{col.title | translate}}
<span title="{{ col.title | translate }}" class="adf-datatable-cell-value">
{{ col.title | translate }}
</span>
}
@if (col.subtitle) {
<span
title="{{col.subtitle | translate}}"
title="{{ col.subtitle | translate }}"
class="adf-datatable-cell-value adf-datatable-cell-header_subtitle"
>
({{col.subtitle | translate}})
({{ col.subtitle | translate }})
</span>
}
@@ -136,29 +140,27 @@
@if (col.header) {
<div class="adf-datatable-cell-value">
<ng-template [ngTemplateOutlet]="col.header" [ngTemplateOutletContext]="{$implicit: col}" />
<ng-template [ngTemplateOutlet]="col.header" [ngTemplateOutletContext]="{ $implicit: col }" />
</div>
}
<span
[class.adf-datatable__header--sorted-asc]="isColumnSorted(col, 'asc')"
[class.adf-datatable__header--sorted-desc]="isColumnSorted(col, 'desc')">
[class.adf-datatable__header--sorted-desc]="isColumnSorted(col, 'desc')"
>
</span>
@if (allowFiltering) {
<ng-template [ngTemplateOutlet]="headerFilterTemplate" [ngTemplateOutletContext]="{$implicit: col}" />
<ng-template [ngTemplateOutlet]="headerFilterTemplate" [ngTemplateOutletContext]="{ $implicit: col }" />
}
@if (col.draggable) {
<span
cdkDragHandle
[ngClass]="{ 'adf-datatable-cell-header-drag-icon': !isResizing }"
>
<span cdkDragHandle [ngClass]="{ 'adf-datatable-cell-header-drag-icon': !isResizing }">
@if (hoveredHeaderColumnIndex === columnIndex && !isResizing) {
<mat-icon
svgIcon="adf:drag_indicator"
class="adf-datatable-cell-header-drag-icon-visible"
[attr.data-automation-id]="'adf-datatable-cell-header-drag-icon-'+col.key"
[attr.data-automation-id]="'adf-datatable-cell-header-drag-icon-' + col.key"
aria-hidden="true"
/>
}
@@ -167,7 +169,11 @@
</div>
@if (isResizingEnabled && col.resizable && !lastColumn) {
<div
[ngClass]="hoveredHeaderColumnIndex === columnIndex && !isResizing || resizingColumnIndex === columnIndex ? 'adf-datatable__resize-handle-visible' : 'adf-datatable__resize-handle-hidden'"
[ngClass]="
(hoveredHeaderColumnIndex === columnIndex && !isResizing) || resizingColumnIndex === columnIndex
? 'adf-datatable__resize-handle-visible'
: 'adf-datatable__resize-handle-hidden'
"
adf-resize-handle
tabindex="0"
role="slider"
@@ -178,17 +184,18 @@
(click)="$event.stopPropagation()"
(keydown)="$event.stopPropagation()"
class="adf-datatable__resize-handle"
[resizableContainer]="resizableElement">
[resizableContainer]="resizableElement"
>
<div class="adf-datatable__resize-handle--divider"></div>
</div>
}
<div class="adf-drop-header-cell-placeholder" *cdkDragPlaceholder></div>
</div>
}
}
}
<!-- Header actions (right) -->
@if ((actions && actionsPosition === 'right') || (mainActionTemplate && showMainDatatableActions)) {
@if ((actions && actionsPosition === 'right') || (mainActionTemplate && showMainDatatableActions)) {
<div
class="adf-actions-column adf-datatable-actions-menu adf-datatable-cell-header adf-datatable__actions-cell"
[class.adf-datatable-actions-menu-provided]="showProvidedActions"
@@ -200,7 +207,8 @@
mat-icon-button
#mainMenuTrigger="matMenuTrigger"
(click)="onMainMenuOpen()"
[matMenuTriggerFor]="mainMenu">
[matMenuTriggerFor]="mainMenu"
>
<mat-icon adf-icon="view_week_outline" />
</button>
<mat-menu #mainMenu (closed)="onMainMenuClosed()">
@@ -209,13 +217,14 @@
[ngTemplateOutlet]="mainActionTemplate"
[ngTemplateOutletContext]="{
$implicit: mainMenuTrigger
}" />
}"
/>
</div>
</mat-menu>
<span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.ACTIONS' | translate }}</span>
}
</div>
}
}
</adf-datatable-row>
</div>
}
@@ -223,12 +232,18 @@
@if (!loading) {
<div
class="adf-datatable-body"
[ngClass]="{ 'adf-blur-datatable-body': blurOnResize && (isDraggingHeaderColumn || isResizing), 'adf-datatable-body__draggable': enableDragRows && !isDraggingRow, 'adf-datatable-body__dragging': isDraggingRow }"
[ngClass]="{
'adf-blur-datatable-body': blurOnResize && (isDraggingHeaderColumn || isResizing),
'adf-datatable-body__draggable': enableDragRows && !isDraggingRow,
'adf-datatable-body__dragging': isDraggingRow
}"
cdkDropList
[cdkDropListDisabled]="!enableDragRows"
role="rowgroup">
role="rowgroup"
>
@if (!noPermission) {
<adf-datatable-row *ngFor="let row of data.getRows(); let idx = index"
<adf-datatable-row
*ngFor="let row of data.getRows(); let idx = index"
cdkDrag
[cdkDragDisabled]="!enableDragRows"
(cdkDragDropped)="onDragDrop($event)"
@@ -255,29 +270,33 @@
>
<!-- Drag button -->
@if (enableDragRows) {
<div
role="gridcell"
class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-hover-only">
<div role="gridcell" class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-hover-only">
<mat-icon adf-icon="drag_indicator" aria-hidden="true" />
</div>
}
<!-- Actions (left) -->
@if (actions && actionsPosition === 'left') {
@if (actions && actionsPosition === 'left') {
<div role="gridcell" class="adf-datatable-cell">
<button mat-icon-button [matMenuTriggerFor]="menu" #actionsMenuTrigger="matMenuTrigger"
[ngClass]="getHideActionsWithoutHoverClass(actionsMenuTrigger)"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_left_' + idx"
[attr.data-automation-id]="'action_menu_' + idx">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
#actionsMenuTrigger="matMenuTrigger"
[ngClass]="getHideActionsWithoutHoverClass(actionsMenuTrigger)"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_left_' + idx"
[attr.data-automation-id]="'action_menu_' + idx"
>
<mat-icon adf-icon="more_vert" />
</button>
<mat-menu #menu="matMenu">
@for (action of getRowActions(row); track action.title) {
<button mat-menu-item
[attr.data-automation-id]="action.title"
[disabled]="action.disabled"
(click)="onExecuteRowAction(row, action)">
<button
mat-menu-item
[attr.data-automation-id]="action.title"
[disabled]="action.disabled"
(click)="onExecuteRowAction(row, action)"
>
@if (action.icon) {
<mat-icon [adf-icon]="action.icon" />
}
@@ -286,9 +305,9 @@
}
</mat-menu>
</div>
}
}
@if (multiselect) {
@if (multiselect) {
<label
(keydown.enter)="onEnterKeyPressed(row, $any($event))"
(click)="onCheckboxLabelClick(row, $event)"
@@ -316,24 +335,28 @@
<span class="adf-sr-only" aria-live="off">
{{ row.isSelected ? ('ADF-DATATABLE.ACCESSIBILITY.SELECTED' | translate) : '' }}
</span>
}
}
@for (col of getVisibleColumns(); track col.key; let lastColumn = $last;) {
@for (col of getVisibleColumns(); track col.key; let lastColumn = $last) {
<div
role="gridcell"
class="adf-datatable-cell adf-datatable-cell--{{col.type || 'text'}} {{col.cssClass}} adf-datatable-cell-data adf-datatable-cell--{{getAutomationValue(row)}}"
class="adf-datatable-cell adf-datatable-cell--{{ col.type || 'text' }} {{
col.cssClass
}} adf-datatable-cell-data adf-datatable-cell--{{ getAutomationValue(row) }}"
[attr.title]="col.title | translate"
[attr.data-automation-id]="getAutomationValue(row)"
[attr.aria-label]="col.title ? (col.title | translate) : null"
[attr.aria-hidden]='isRepresentationContent(col) ? "true" : null'
[attr.aria-hidden]="isRepresentationContent(col) ? 'true' : null"
(click)="onRowClick(row, $event)"
[attr.tabindex]="null"
(keydown.enter)="onEnterKeyPressed(row, $any($event))"
[adf-context-menu]="getContextMenuActions(row, col)"
[adf-context-menu-enabled]="contextMenu"
adf-drop-zone dropTarget="cell" [dropColumn]="col" [dropRow]="row"
[ngStyle]="(col.width) && !lastColumn && {'flex': getFlexValue(col)}"
adf-drop-zone
dropTarget="cell"
[dropColumn]="col"
[dropRow]="row"
[ngStyle]="col.width && !lastColumn && { flex: getFlexValue(col) }"
>
@if (!col.template) {
<div class="adf-datatable-cell-container">
@@ -342,7 +365,7 @@
<div class="adf-cell-value">
@if (isIconValue(row, col)) {
<mat-icon
[attr.aria-label]="col.srTitle? (col.srTitle | translate) : null"
[attr.aria-label]="col.srTitle ? (col.srTitle | translate) : null"
[attr.aria-hidden]="!asIconValue(row, col)"
[adf-icon]="asIconValue(row, col)"
/>
@@ -350,19 +373,35 @@
@if (row.isSelected && !multiselect) {
<mat-icon class="adf-datatable-selected" svgIcon="selected" />
} @else {
<img class="adf-datatable-center-img-ie"
[attr.aria-label]="(data.getValue(row, col) | fileType) === 'disable' ?
('ADF-DATATABLE.ACCESSIBILITY.ICON_DISABLED' | translate) :
'ADF-DATATABLE.ACCESSIBILITY.ICON_TEXT' | translate:{
type: 'ADF-DATATABLE.FILE_TYPE.' + (data.getValue(row, col) | fileType | uppercase) | translate
}"
[attr.alt]="(data.getValue(row, col) | fileType) === 'disable' ?
('ADF-DATATABLE.ACCESSIBILITY.ICON_DISABLED' | translate) :
'ADF-DATATABLE.ACCESSIBILITY.ICON_TEXT' | translate:{
type: 'ADF-DATATABLE.FILE_TYPE.' + (data.getValue(row, col) | fileType | uppercase) | translate
}"
<img
class="adf-datatable-center-img-ie"
[attr.aria-label]="
(data.getValue(row, col) | fileType) === 'disable'
? ('ADF-DATATABLE.ACCESSIBILITY.ICON_DISABLED' | translate)
: ('ADF-DATATABLE.ACCESSIBILITY.ICON_TEXT'
| translate
: {
type:
'ADF-DATATABLE.FILE_TYPE.' +
(data.getValue(row, col) | fileType | uppercase)
| translate
})
"
[attr.alt]="
(data.getValue(row, col) | fileType) === 'disable'
? ('ADF-DATATABLE.ACCESSIBILITY.ICON_DISABLED' | translate)
: ('ADF-DATATABLE.ACCESSIBILITY.ICON_TEXT'
| translate
: {
type:
'ADF-DATATABLE.FILE_TYPE.' +
(data.getValue(row, col) | fileType | uppercase)
| translate
})
"
src="{{ data.getValue(row, col) }}"
(error)="onImageLoadingError($event, row)">
(error)="onImageLoadingError($event, row)"
/>
}
}
</div>
@@ -381,59 +420,74 @@
@case ('date') {
<div
class="adf-cell-value adf-cell-date"
[attr.data-automation-id]="'date_' + (data.getValue(row, col, resolverFn) | adfLocalizedDate: 'medium') ">
<adf-date-cell class="adf-datatable-center-date-column-ie"
[attr.data-automation-id]="
'date_' + (data.getValue(row, col, resolverFn) | adfLocalizedDate: 'medium')
"
>
<adf-date-cell
class="adf-datatable-center-date-column-ie"
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)"
[dateConfig]="col.dateConfig" />
[dateConfig]="col.dateConfig"
/>
</div>
}
@case ('location') {
<div class="adf-cell-value"
[attr.data-automation-id]="'location' + data.getValue(row, col, resolverFn)">
<div
class="adf-cell-value"
[attr.data-automation-id]="'location' + data.getValue(row, col, resolverFn)"
>
<adf-location-cell
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)" />
[tooltip]="getCellTooltip(row, col)"
/>
</div>
}
@case ('fileSize') {
<div class="adf-cell-value"
[attr.data-automation-id]="'fileSize_' + data.getValue(row, col, resolverFn)">
<adf-filesize-cell class="adf-datatable-center-size-column-ie"
<div
class="adf-cell-value"
[attr.data-automation-id]="'fileSize_' + data.getValue(row, col, resolverFn)"
>
<adf-filesize-cell
class="adf-datatable-center-size-column-ie"
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)" />
[tooltip]="getCellTooltip(row, col)"
/>
</div>
}
@case ('text') {
<div class="adf-cell-value"
[attr.data-automation-id]="'text_' + data.getValue(row, col, resolverFn)">
<div class="adf-cell-value" [attr.data-automation-id]="'text_' + data.getValue(row, col, resolverFn)">
<adf-datatable-cell
[copyContent]="col.copyContent"
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)" />
[tooltip]="getCellTooltip(row, col)"
/>
</div>
}
@case ('boolean') {
<div class="adf-cell-value"
[attr.data-automation-id]="'boolean_' + data.getValue(row, col, resolverFn)">
<div
class="adf-cell-value"
[attr.data-automation-id]="'boolean_' + data.getValue(row, col, resolverFn)"
>
<adf-boolean-cell
[data]="data"
[column]="col"
[row]="row"
[resolverFn]="resolverFn"
[tooltip]="getCellTooltip(row, col)" />
[tooltip]="getCellTooltip(row, col)"
/>
</div>
}
@case ('json') {
@@ -443,31 +497,36 @@
[data]="data"
[column]="col"
[resolverFn]="resolverFn"
[row]="row" />
[row]="row"
/>
</div>
}
@case ('amount') {
<div
class="adf-cell-value"
[attr.data-automation-id]="'amount_' + data.getValue(row, col, resolverFn)">
[attr.data-automation-id]="'amount_' + data.getValue(row, col, resolverFn)"
>
<adf-amount-cell
[data]="data"
[column]="col"
[resolverFn]="resolverFn"
[row]="row"
[currencyConfig]="col.currencyConfig" />
[currencyConfig]="col.currencyConfig"
/>
</div>
}
@case ('number') {
<div
class="adf-cell-value"
[attr.data-automation-id]="'number_' + data.getValue(row, col, resolverFn)">
[attr.data-automation-id]="'number_' + data.getValue(row, col, resolverFn)"
>
<adf-number-cell
[data]="data"
[column]="col"
[resolverFn]="resolverFn"
[row]="row"
[decimalConfig]="col.decimalConfig" />
[decimalConfig]="col.decimalConfig"
/>
</div>
}
@default {
@@ -482,7 +541,11 @@
<div class="adf-cell-value">
<ng-container
[ngTemplateOutlet]="col.template"
[ngTemplateOutletContext]="{ $implicit: { data: data, row: row, col: col }, value: data.getValue(row, col, resolverFn) }" />
[ngTemplateOutletContext]="{
$implicit: { data: data, row: row, col: col },
value: data.getValue(row, col, resolverFn)
}"
/>
</div>
</div>
}
@@ -490,28 +553,34 @@
}
<!-- Row actions (right) -->
@if (!showProvidedActions && ((actions && actionsPosition === 'right') || (mainActionTemplate && showMainDatatableActions))) {
@if (!showProvidedActions && ((actions && actionsPosition === 'right') || (mainActionTemplate && showMainDatatableActions))) {
<div
role="gridcell"
class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-center-actions-column-ie adf-datatable-actions-menu">
class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-center-actions-column-ie adf-datatable-actions-menu"
>
@if (actions && actionsPosition === 'right') {
<button mat-icon-button [matMenuTriggerFor]="menu" #actionsMenuTrigger="matMenuTrigger"
[ngClass]="getHideActionsWithoutHoverClass(actionsMenuTrigger)"
[attr.aria-label]="'ADF-DATATABLE.ACCESSIBILITY.ROW_OPTION_BUTTON' | translate"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_right_' + idx"
[attr.data-automation-id]="'action_menu_' + idx"
(keydown.enter)="actionsMenuTrigger.openMenu()">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
#actionsMenuTrigger="matMenuTrigger"
[ngClass]="getHideActionsWithoutHoverClass(actionsMenuTrigger)"
[attr.aria-label]="'ADF-DATATABLE.ACCESSIBILITY.ROW_OPTION_BUTTON' | translate"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_right_' + idx"
[attr.data-automation-id]="'action_menu_' + idx"
(keydown.enter)="actionsMenuTrigger.openMenu()"
>
<mat-icon adf-icon="more_vert" />
</button>
<mat-menu #menu="matMenu">
@for (action of getRowActions(row); track action.title) {
<button mat-menu-item
[attr.data-automation-id]="action.title"
[attr.aria-label]="action.title | translate"
[disabled]="action.disabled"
(click)="onExecuteRowAction(row, action)">
<button
mat-menu-item
[attr.data-automation-id]="action.title"
[attr.aria-label]="action.title | translate"
[disabled]="action.disabled"
(click)="onExecuteRowAction(row, action)"
>
@if (action.icon) {
<mat-icon [adf-icon]="action.icon" />
}
@@ -521,29 +590,23 @@
</mat-menu>
}
</div>
}
}
</adf-datatable-row>
@if (isEmpty()) {
<div role="row" class="adf-datatable-row">
<div class="adf-no-content-container adf-datatable-cell" role="gridcell">
@if (noContentTemplate) {
<ng-template
ngFor [ngForOf]="[data]"
[ngForTemplate]="noContentTemplate" />
<ng-template ngFor [ngForOf]="[data]" [ngForTemplate]="noContentTemplate" />
}
<ng-content select="adf-empty-list" />
</div>
</div>
}
} @else {
<div
role="row"
class="adf-datatable-row adf-no-permission__row">
<div role="row" class="adf-datatable-row adf-no-permission__row">
<div class="adf-no-permission__cell adf-no-content-container adf-datatable-cell">
@if (noPermissionTemplate) {
<ng-template
ngFor [ngForOf]="[data]"
[ngForTemplate]="noPermissionTemplate" />
<ng-template ngFor [ngForOf]="[data]" [ngForTemplate]="noPermissionTemplate" />
}
</div>
</div>
@@ -553,9 +616,7 @@
<div class="adf-datatable-row adf-datatable-data-loading">
<div class="adf-no-content-container adf-datatable-cell">
@if (loadingTemplate) {
<ng-template
ngFor [ngForOf]="[data]"
[ngForTemplate]="loadingTemplate" />
<ng-template ngFor [ngForOf]="[data]" [ngForTemplate]="loadingTemplate" />
}
</div>
</div>
@@ -59,7 +59,7 @@ import { ObjectDataTableAdapter } from '../../data/object-datatable-adapter';
import { DataCellEvent } from '../data-cell.event';
import { DataRowActionEvent } from '../data-row-action.event';
import { buffer, debounceTime, filter, map, share } from 'rxjs/operators';
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDragPlaceholder, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { ResizeEvent } from '../../directives/resizable/types';
@@ -122,7 +122,8 @@ export type ShowHeaderMode = (typeof ShowHeaderMode)[keyof typeof ShowHeaderMode
JsonCellComponent,
AmountCellComponent,
NumberCellComponent,
MatTooltipModule
MatTooltipModule,
CdkDragPlaceholder
],
templateUrl: './datatable.component.html',
styleUrls: ['./datatable.component.scss'],
@@ -1,27 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NgModule } from '@angular/core';
import { EditJsonDialogComponent } from './edit-json.dialog';
/* @deprecated Use EditJsonDialogComponent directly */
@NgModule({
declarations: [],
imports: [EditJsonDialogComponent],
exports: [EditJsonDialogComponent]
})
export class EditJsonDialogModule {}
-3
View File
@@ -16,13 +16,10 @@
*/
export * from './edit-json/edit-json.dialog';
export * from './edit-json/edit-json.dialog.module';
export * from './unsaved-changes-dialog/unsaved-changes-dialog.component';
export * from './unsaved-changes-dialog/unsaved-changes-dialog.module';
export * from './unsaved-changes-dialog/unsaved-changes.guard';
export * from './confirm-dialog/confirm.dialog';
export * from './confirm-dialog/confirm.dialog.module';
export * from './dialog';
@@ -1,26 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NgModule } from '@angular/core';
import { UnsavedChangesDialogComponent } from './unsaved-changes-dialog.component';
/** @deprecated import `UnsavedChangesDialogComponent` instead */
@NgModule({
imports: [UnsavedChangesDialogComponent],
exports: [UnsavedChangesDialogComponent]
})
export class UnsavedChangesDialogModule {}
@@ -1,4 +1,4 @@
<div @tooltip class="adf-tooltip-card" [style.width.px]="width">
<div class="adf-tooltip-card" [style.width.px]="width">
<img *ngIf="image " [src]="image" [width]="width" alt="{{text}}">
<hr *ngIf="image" />
<p *ngIf="text">{{text}}</p>
@@ -1,7 +1,18 @@
@use '@angular/material' as mat;
@keyframes tooltip-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
:host {
display: block;
animation: tooltip-fade-in 200ms ease-out;
}
div.adf-tooltip-card {
@@ -16,7 +16,6 @@
*/
import { Component, Input, SecurityContext, inject } from '@angular/core';
import { animate, style, transition, trigger } from '@angular/animations';
import { DomSanitizer } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
@@ -24,13 +23,7 @@ import { CommonModule } from '@angular/common';
selector: 'adf-tooltip-card-component',
imports: [CommonModule],
templateUrl: './tooltip-card.component.html',
styleUrls: ['./tooltip-card.component.scss'],
animations: [
trigger('tooltip', [
transition(':enter', [style({ opacity: 0 }), animate(200, style({ opacity: 1 }))]),
transition(':leave', [animate(200, style({ opacity: 0 }))])
])
]
styleUrls: ['./tooltip-card.component.scss']
})
export class TooltipCardComponent {
private readonly sanitizer = inject(DomSanitizer);
@@ -1,10 +1,14 @@
<div class="adf-error-container adf-error-widget-container">
<div *ngIf="error?.isActive()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ error.message | translate:translateParameters }}</div>
</div>
<div *ngIf="required" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ required }}</div>
</div>
@if (error?.isActive()) {
<div class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ error.message | translate: translateParameters }}</div>
</div>
}
@if (required) {
<div class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ required }}</div>
</div>
}
</div>
@@ -1,3 +1,15 @@
@keyframes adf-error-slide-in-down {
from {
opacity: 0;
transform: translateY(-100%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.adf-error {
display: flex;
align-items: center;
@@ -6,6 +18,10 @@
height: auto;
}
&-animate {
animation: adf-error-slide-in-down 300ms cubic-bezier(0.55, 0, 0.55, 0.2);
}
&-container {
padding-top: 0;
color: var(--mat-sys-error);
@@ -50,12 +50,6 @@ describe('ErrorWidgetComponent', () => {
expect(errorIcon).toEqual('error_outline');
});
it('should set subscriptAnimationState value', () => {
widget.ngOnChanges(errorChanges);
expect(widget.subscriptAnimationState).toEqual('enter');
});
it('should check proper error message', async () => {
widget.ngOnChanges(errorChanges);
@@ -17,8 +17,6 @@
/* eslint-disable @angular-eslint/component-selector */
import { animate, state, style, transition, trigger } from '@angular/animations';
import { NgIf } from '@angular/common';
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { TranslatePipe } from '@ngx-translate/core';
import { ErrorMessageModel } from '../core';
@@ -29,18 +27,6 @@ import { IconModule } from '../../../../icon/icon.module';
selector: 'error-widget',
templateUrl: './error.component.html',
styleUrls: ['./error.component.scss'],
animations: [
trigger('transitionMessages', [
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
transition('void => enter', [
style({
opacity: 0,
transform: 'translateY(-100%)'
}),
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
])
])
],
host: {
'(click)': 'event($event)',
'(blur)': 'event($event)',
@@ -52,7 +38,7 @@ import { IconModule } from '../../../../icon/icon.module';
'(invalid)': 'event($event)',
'(select)': 'event($event)'
},
imports: [NgIf, IconModule, TranslatePipe],
imports: [IconModule, TranslatePipe],
encapsulation: ViewEncapsulation.None
})
export class ErrorWidgetComponent extends WidgetComponent implements OnChanges {
@@ -63,18 +49,15 @@ export class ErrorWidgetComponent extends WidgetComponent implements OnChanges {
required: string;
translateParameters: any = null;
subscriptAnimationState: string = '';
ngOnChanges(changes: SimpleChanges) {
if (changes['required']) {
this.required = changes.required.currentValue;
this.subscriptAnimationState = 'enter';
}
if (changes['error']?.currentValue) {
if (changes.error.currentValue.isActive()) {
this.error = changes.error.currentValue;
this.translateParameters = this.error.getAttributesAsJsonObj();
this.subscriptAnimationState = 'enter';
}
}
}
@@ -1,16 +1,17 @@
<mat-sidenav-container class="adf-layout-container" autosize>
<mat-sidenav
class="adf-layout-container-sidenav"
[ngClass]="sidenavAnimationState?.value"
[position]="position"
[disableClose]="!isMobileScreenSize"
[@sidenavAnimation]="sidenavAnimationState"
[opened]="!isMobileScreenSize && !hideSidenav"
[mode]="isMobileScreenSize ? 'over' : 'side'">
[mode]="isMobileScreenSize ? 'over' : 'side'"
[style.width.px]="sidenavAnimationState?.params?.width">
<ng-content sidenav select="[app-layout-navigation]" />
</mat-sidenav>
<div>
<div class="adf-container-full-width" [@contentAnimationLeft]="getContentAnimationState()">
<div class="adf-container-full-width" [ngClass]="contentAnimationState?.value" [style.margin-left.px]="contentAnimationState?.params?.['margin-left']" [style.margin-right.px]="contentAnimationState?.params?.['margin-right']">
<ng-content select="[app-layout-content]" />
</div>
</div>
@@ -11,12 +11,16 @@ adf-layout-container {
border-right: 1px solid var(--mat-sys-outline-variant);
background: var(--mat-sys-surface);
color: var(--mat-sys-on-surface);
transition: width 400ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
}
.adf-container-full-width {
width: inherit;
overflow: hidden;
transition:
margin-left 400ms cubic-bezier(0.25, 0.8, 0.25, 1),
margin-right 400ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
/* query for Microsoft IE 11 */
@@ -19,40 +19,13 @@ import { Component, Input, ViewChild, OnInit, OnDestroy, ViewEncapsulation, OnCh
import { MatSidenav, MatSidenavModule } from '@angular/material/sidenav';
import { Direction } from '@angular/cdk/bidi';
import { CommonModule } from '@angular/common';
import { animate, state, style, transition, trigger } from '@angular/animations';
@Component({
selector: 'adf-layout-container',
imports: [CommonModule, MatSidenavModule],
templateUrl: './layout-container.component.html',
styleUrls: ['./layout-container.component.scss'],
encapsulation: ViewEncapsulation.None,
animations: [
trigger('sidenavAnimation', [
state('expanded', style({ width: '{{ width }}px' }), { params: { width: 0 } }),
state('compact', style({ width: '{{ width }}px' }), { params: { width: 0 } }),
transition('compact <=> expanded', animate('0.4s cubic-bezier(0.25, 0.8, 0.25, 1)'))
]),
trigger('contentAnimationLeft', [
state(
'expanded',
style({
'margin-left': '{{ margin-left }}px',
'margin-right': '{{ margin-right }}px'
}),
{ params: { 'margin-left': 0, 'margin-right': 0 } }
),
state(
'compact',
style({
'margin-left': '{{ margin-left }}px',
'margin-right': '{{ margin-right }}px'
}),
{ params: { 'margin-left': 0, 'margin-right': 0 } }
),
transition('expanded <=> compact', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))
])
]
encapsulation: ViewEncapsulation.None
})
export class LayoutContainerComponent implements OnInit, OnDestroy, OnChanges {
@Input() sidenavMin: number;
@@ -118,10 +91,6 @@ export class LayoutContainerComponent implements OnInit, OnDestroy, OnChanges {
return !!this.mediaQueryList?.matches;
}
getContentAnimationState(): any {
return this.contentAnimationState;
}
private get toggledSidenavAnimation(): any {
return this.sidenavAnimationState === this.SIDENAV_STATES.EXPANDED ? this.SIDENAV_STATES.COMPACT : this.SIDENAV_STATES.EXPANDED;
}
@@ -1,41 +0,0 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { trigger, transition, animate, style, state, AnimationTriggerMetadata } from '@angular/animations';
export const searchAnimation: AnimationTriggerMetadata = trigger('transitionMessages', [
state(
'active',
style({
'margin-left': '{{ margin-left }}px',
'margin-right': '{{ margin-right }}px',
transform: '{{ transform }}'
}),
{ params: { 'margin-left': 0, 'margin-right': 0, transform: 'translateX(0%)' } }
),
state(
'inactive',
style({
'margin-left': '{{ margin-left }}px',
'margin-right': '{{ margin-right }}px',
transform: '{{ transform }}'
}),
{ params: { 'margin-left': 0, 'margin-right': 0, transform: 'translateX(0%)' } }
),
state('no-animation', style({ transform: 'translateX(0%)', width: '100%' })),
transition('active <=> inactive', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))
]);
@@ -15,7 +15,6 @@
* limitations under the License.
*/
export * from './animations';
export * from './search-text-input.component';
export * from './search-trigger.directive';
export * from './search-text-input.module';
@@ -1,44 +1,52 @@
<div class="adf-search-container" [attr.state]="subscriptAnimationState.value">
<div class="adf-search-container-transition"
[@transitionMessages]="subscriptAnimationState"
(@transitionMessages.done)="applySearchFocus($event)">
<button mat-icon-button
*ngIf="expandable && !isSearchBarActive()"
<div class="adf-search-container-transition" [class.adf-search-active]="isSearchBarActive()" [ngStyle]="subscriptAnimationState.params">
@if (expandable && !isSearchBarActive()) {
<button
mat-icon-button
id="adf-search-button"
class="adf-search-button"
[ngClass]="{'adf-search-button-inactive': subscriptAnimationState.value === 'inactive'}"
[ngClass]="{ 'adf-search-button-inactive': subscriptAnimationState.value === 'inactive' }"
[title]="'CORE.SEARCH.BUTTON.TOOLTIP' | translate"
(click)="toggleSearchBar()"
(keyup.enter)="toggleSearchBar()">
<mat-icon [attr.aria-label]="'CORE.SEARCH.BUTTON.ARIA-LABEL' | translate" adf-icon="search" />
</button>
(keyup.enter)="toggleSearchBar()"
>
<mat-icon [attr.aria-label]="'CORE.SEARCH.BUTTON.ARIA-LABEL' | translate" adf-icon="search" />
</button>
}
<mat-form-field class="adf-input-form-field-divider" [hintLabel]="hintLabel">
<mat-label *ngIf='label'>{{label}}</mat-label>
<input matInput
#searchInput
[attr.aria-label]="'CORE.SEARCH.INPUT.ARIA-LABEL' | translate"
[attr.type]="inputType"
[autocomplete]="getAutoComplete()"
id="adf-control-input"
[(ngModel)]="searchTerm"
[placeholder]="placeholder"
(focus)="activateToolbar()"
(blur)="onBlur($event)"
(keyup.escape)="toggleSearchBar()"
(keyup.arrowdown)="selectFirstResult($event)"
(ngModelChange)="inputChange($event)"
[searchAutocomplete]="searchAutocomplete ? searchAutocomplete : null"
(keyup.enter)="searchSubmit($event)">
<button mat-icon-button
*ngIf="canShowClearSearch()"
@if (label) {
<mat-label>{{ label }}</mat-label>
}
<input
matInput
#searchInput
[attr.aria-label]="'CORE.SEARCH.INPUT.ARIA-LABEL' | translate"
[attr.type]="inputType"
[autocomplete]="getAutoComplete()"
id="adf-control-input"
[(ngModel)]="searchTerm"
[placeholder]="placeholder"
(focus)="activateToolbar()"
(blur)="onBlur($event)"
(keyup.escape)="toggleSearchBar()"
(keyup.arrowdown)="selectFirstResult($event)"
(ngModelChange)="inputChange($event)"
[searchAutocomplete]="searchAutocomplete ? searchAutocomplete : null"
(keyup.enter)="searchSubmit($event)"
/>
@if (canShowClearSearch()) {
<button
mat-icon-button
matSuffix
data-automation-id="adf-clear-search-button"
class="adf-clear-search-button"
[title]="'CORE.SEARCH.FILTER.BUTTONS.CLOSE' | translate"
(click)="resetSearch()"
(keyup.enter)="resetSearch()">
(keyup.enter)="resetSearch()"
>
<mat-icon adf-icon="close" />
</button>
}
</mat-form-field>
</div>
</div>
@@ -6,6 +6,10 @@
.adf-search-container-transition {
display: flex;
align-items: center;
transition:
transform 400ms cubic-bezier(0.25, 0.8, 0.25, 1),
margin-left 400ms cubic-bezier(0.25, 0.8, 0.25, 1),
margin-right 400ms cubic-bezier(0.25, 0.8, 0.25, 1);
}
.adf {
@@ -164,7 +164,7 @@ describe('SearchTextInputComponent', () => {
function testMarginValue(isLtr: boolean): void {
userPreferencesService.setWithoutStore('textOrientation', isLtr ? 'ltr' : 'rtl');
clickSearchButton();
const expectedResult = isLtr ? { 'margin-left': 0 } : { 'margin-right': 0 };
const expectedResult = isLtr ? { 'margin-left': '0px' } : { 'margin-right': '0px' };
expect(component.subscriptAnimationState.params).toEqual(expectedResult);
discardPeriodicTasks();
}
@@ -16,7 +16,7 @@
*/
import { Direction } from '@angular/cdk/bidi';
import { NgClass, NgIf } from '@angular/common';
import { NgClass, NgStyle } from '@angular/common';
import {
Component,
DestroyRef,
@@ -24,8 +24,8 @@ import {
EventEmitter,
inject,
Input,
OnDestroy,
OnInit,
OnDestroy,
Output,
ViewChild,
ViewEncapsulation
@@ -35,10 +35,9 @@ import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { TranslatePipe } from '@ngx-translate/core';
import { Observable, Subject, Subscription } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { debounceTime, filter } from 'rxjs/operators';
import { UserPreferencesService } from '../common';
import { searchAnimation } from './animations';
import { SearchAnimationDirection, SearchAnimationState, SearchTextStateEnum } from './models/search-text-input.model';
import { SearchTriggerDirective } from './search-trigger.directive';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -48,9 +47,8 @@ import { IconModule } from '../icon/icon.module';
selector: 'adf-search-text-input',
templateUrl: './search-text-input.component.html',
styleUrls: ['./search-text-input.component.scss'],
animations: [searchAnimation],
encapsulation: ViewEncapsulation.None,
imports: [MatButtonModule, IconModule, TranslatePipe, MatFormFieldModule, MatInputModule, FormsModule, SearchTriggerDirective, NgIf, NgClass],
imports: [MatButtonModule, IconModule, TranslatePipe, MatFormFieldModule, MatInputModule, FormsModule, SearchTriggerDirective, NgClass, NgStyle],
host: {
class: 'adf-search-text-input'
}
@@ -91,7 +89,7 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
/** Listener for results-list events (focus, blur and focusout). */
@Input()
focusListener: Observable<FocusEvent>;
focusListener: Observable<FocusEvent> | null = null;
/** Collapse search bar on submit. */
@Input()
@@ -147,44 +145,44 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
/** Emitted when the search visibility changes. True when the search is active, false when it is inactive */
@Output()
searchVisibility: EventEmitter<boolean> = new EventEmitter<boolean>();
searchVisibility = new EventEmitter<boolean>();
@ViewChild('searchInput', { static: true })
searchInput: ElementRef;
subscriptAnimationState: any;
searchInput!: ElementRef;
animationStates: SearchAnimationDirection = {
ltr: {
active: { value: 'active', params: { 'margin-left': 13 } },
active: { value: 'active', params: { 'margin-left': '13px' } },
inactive: { value: 'inactive', params: { transform: 'translateX(100%)' } }
},
rtl: {
active: { value: 'active', params: { 'margin-right': 13 } },
active: { value: 'active', params: { 'margin-right': '13px' } },
inactive: { value: 'inactive', params: { transform: 'translateX(-100%)' } }
}
};
private dir = 'ltr';
private toggleSearch = new Subject<any>();
private focusSubscription: Subscription;
subscriptAnimationState: SearchAnimationState = this.animationStates.ltr.inactive;
private dir: keyof SearchAnimationDirection = 'ltr';
private readonly toggleSearch = new Subject<any>();
private readonly valueChange = new Subject<string>();
private readonly toggleSubscription: Subscription;
toggle$ = this.toggleSearch.asObservable();
private readonly destroyRef = inject(DestroyRef);
constructor() {
this.toggleSubscription = this.toggle$.pipe(debounceTime(200), takeUntilDestroyed()).subscribe(() => {
this.toggle$.pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
if (this.expandable) {
this.subscriptAnimationState = this.toggleAnimation();
if (this.subscriptAnimationState.value === 'inactive') {
this.searchTerm = '';
this.reset.emit(true);
if (document.activeElement.id === this.searchInput.nativeElement.id) {
if (document.activeElement?.id === this.searchInput.nativeElement.id) {
this.searchInput.nativeElement.blur();
}
} else if (this.subscriptAnimationState.value === 'active' && this.isDefaultStateCollapsed()) {
setTimeout(() => this.searchInput.nativeElement.focus(), 0);
}
this.emitVisibilitySearch();
}
@@ -205,10 +203,9 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
this.setupFocusEventHandlers();
}
applySearchFocus(animationDoneEvent) {
if (animationDoneEvent.toState === 'active' && this.isDefaultStateCollapsed()) {
this.searchInput.nativeElement.focus();
}
ngOnDestroy() {
this.toggleSearch.complete();
this.valueChange.complete();
}
getAutoComplete(): string {
@@ -218,23 +215,23 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
private toggleAnimation() {
if (this.dir === 'ltr') {
return this.subscriptAnimationState.value === 'inactive'
? { value: 'active', params: { 'margin-left': 0 } }
? { value: 'active', params: { 'margin-left': '0px' } }
: { value: 'inactive', params: { transform: 'translateX(100%)' } };
} else {
return this.subscriptAnimationState.value === 'inactive'
? { value: 'active', params: { 'margin-right': 0 } }
? { value: 'active', params: { 'margin-right': '0px' } }
: { value: 'inactive', params: { transform: 'translateX(-100%)' } };
}
}
private getDefaultState(dir: string): SearchAnimationState {
private getDefaultState(dir: keyof SearchAnimationDirection): SearchAnimationState {
if (this.dir) {
return this.getAnimationState(dir);
}
return this.animationStates.ltr.inactive;
}
private getAnimationState(dir: string): SearchAnimationState {
private getAnimationState(dir: keyof SearchAnimationDirection): SearchAnimationState {
if (this.expandable && this.isDefaultStateExpanded()) {
return this.animationStates[dir].active;
} else if (this.expandable) {
@@ -246,21 +243,21 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
private setupFocusEventHandlers() {
if (this.focusListener) {
const focusEvents: Observable<FocusEvent> = this.focusListener.pipe(
debounceTime(50),
filter(
($event: any) => this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout' || $event.type === 'focus')
),
takeUntilDestroyed(this.destroyRef)
);
this.focusSubscription = focusEvents.subscribe((event: FocusEvent) => {
if (event.type === 'focus') {
this.searchInput.nativeElement.focus();
} else {
this.toggleSearchBar();
}
});
this.focusListener
.pipe(
debounceTime(50),
filter(
($event: any) => this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout' || $event.type === 'focus')
),
takeUntilDestroyed(this.destroyRef)
)
.subscribe((event: FocusEvent) => {
if (event.type === 'focus') {
this.searchInput.nativeElement.focus();
} else {
this.toggleSearchBar();
}
});
}
}
@@ -270,11 +267,11 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
});
}
selectFirstResult($event) {
selectFirstResult($event: any) {
this.selectResult.emit($event);
}
onBlur($event) {
onBlur($event: any) {
if (this.collapseOnBlur && !$event.relatedTarget) {
this.resetSearch();
}
@@ -308,20 +305,6 @@ export class SearchTextInputComponent implements OnInit, OnDestroy {
return this.subscriptAnimationState.value === 'active';
}
ngOnDestroy() {
if (this.toggleSearch) {
this.toggleSubscription.unsubscribe();
this.toggleSearch.complete();
this.toggleSearch = null;
}
if (this.focusSubscription) {
this.focusSubscription.unsubscribe();
this.focusSubscription = null;
this.focusListener = null;
}
}
canShowClearSearch(): boolean {
return this.showClearButton && this.isSearchBarActive();
}
@@ -15,12 +15,13 @@
* limitations under the License.
*/
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { NoopTranslateModule } from './noop-translate.module';
import { NgModule } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { BrowserTestingModule } from '@angular/platform-browser/testing';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
@NgModule({
imports: [BrowserDynamicTestingModule, NoopTranslateModule, NoopAnimationsModule]
imports: [BrowserTestingModule, NoopTranslateModule],
providers: [provideNoopAnimations()]
})
export class GlobalTestingModule {}