File

projects/angular/components/ui-grid/src/ui-grid.component.ts

Extends

ResizableGrid

Implements

AfterContentInit OnChanges OnDestroy

Metadata

Index

Properties
Methods
Inputs
Outputs
Accessors

Inputs

collapseFiltersCount
Type : number

Configure if the grid search filters are eager or on open.

collapsibleFilters
Type : boolean

Option to have collapsible filters.

customFilterValue
Type : []
data

The data list that needs to be rendered within the grid.

NOTE: to have access to all functionality, we recommend that entities display in the grid implement the IGridDataEntry interface.

disabled
Type : boolean
Default value : false

Marks the grid enabled state.

disableSelectionByEntry
Type : function

Configure a function that receives the whole grid row, and returns disabled message if the row should not be selectable

expandedEntries

Set the expanded entry / entries.

expandedEntry

Set the expanded entry.

expandMode
Type : "preserve" | "collapse"
Default value : 'collapse'

Configure if the expanded entry should replace the active row, or add a new row with the expanded view.

fetchStrategy

Configure if the grid search filters are eager or on open.

isProjected
Type : boolean

Marks the grid projected state.

isResizing
Type : any

Marks the grid resizing state.

loading
Type : boolean
Default value : false

Marks the grid loading state.

multiPageSelect
Type : boolean
Default value : false

Configure if the grid allows multi-page selection.

noDataMessage
Type : string

Provide a custom noDataMessage.

refreshable
Type : boolean
Default value : true

Configure if the grid is refreshable.

reset
Type : function
Default value : () => { if (this.header) { this.header.searchValue = ''; } this.filterManager.clear(); this.sortManager.clear(); return of(true); }

Clear search term, filters and sorting and emits true after.

resizeStrategy

The desired resize strategy.

FIXME: Currently only ImmediateNeighbourHalt is stable.

rowSize
Type : number

Configure the row item size for virtualScroll

selectable
Type : boolean
Default value : true

Configure if the grid allows item selection.

showHeaderRow
Type : boolean
Default value : true

Configure if ui-grid-header-row should be visible, by default it is visible

showPaintTime
Type : boolean
Default value : false

Show paint time stats

toggleColumns
Type : boolean
Default value : false

Configure if the grid allows to toggle column visibility.

useCardView
Type : boolean
Default value : false

Configure if Card view should be used

useLegacyDesign
Type : boolean

Option to select an alternate layout for footer pagination.

virtualScroll
Type : boolean
Default value : false

Configure if virtualScroll is enabled.

Outputs

refresh
Type : EventEmitter

Emits an event when user click the refresh button.

removeCustomFilter
Type : EventEmitter
rendered
Type : EventEmitter

Emits an event once the grid has been rendered.

resizeEnd
Type : EventEmitter

Emits an event once the grid has been rendered.

rowClick
Type : EventEmitter

Emits an event when a row is clicked.

sortChange
Type : EventEmitter

Emits an event with the sort model when a column sort changes.

Methods

checkboxLabel
checkboxLabel(row?: T)

Determines the checkbox aria-label`. DEPRECATED

Parameters :
Name Type Optional Description
row T Yes

The row for which the label is computed.

Returns : string
checkboxTooltip
checkboxTooltip(row?: T)

Determines the checkbox matToolTip.

Parameters :
Name Type Optional Description
row T Yes

The row for which the label is computed.

Returns : string
checkIndeterminateState
checkIndeterminateState(indeterminateState: boolean)
Parameters :
Name Type Optional
indeterminateState boolean No
Returns : void
checkShift
checkShift(event: Event)

Marks if the Shift key is pressed.

Parameters :
Name Type Optional
event Event No
Returns : void
clearCustomFilter
clearCustomFilter()
Returns : void
focusRowHeader
focusRowHeader()
Returns : void
getColumnName
getColumnName(column: UiGridColumnDirective<T>, prefix: string)
Parameters :
Name Type Optional Default value
column UiGridColumnDirective<T> No
prefix string No 'ui-grid-dropdown-filter'
Returns : string
handleSelection
handleSelection(idx: number, entry: T)

Handles row selection, and reacts if the Shift key is pressed.

Parameters :
Name Type Optional Description
idx number No

The clicked row index.

entry T No

The entry associated to the selected row.

Returns : void
hideColumnHeaderTooltip
hideColumnHeaderTooltip(tooltip: MatTooltip)
Parameters :
Name Type Optional
tooltip MatTooltip No
Returns : void
isFilterApplied
isFilterApplied(column: UiGridColumnDirective<T>)
Parameters :
Name Type Optional
column UiGridColumnDirective<T> No
Returns : boolean
isRowExpanded
isRowExpanded(rowId?)
Parameters :
Name Optional
rowId Yes
Returns : any
onRowClick
onRowClick(event: Event, row: T)
Parameters :
Name Type Optional
event Event No
row T No
Returns : void
searchableDropdownValue
searchableDropdownValue(searchableDropdown: UiGridSearchFilterDirective<T>)
Parameters :
Name Type Optional
searchableDropdown UiGridSearchFilterDirective<T> No
Returns : ISuggestValue[]
toggle
toggle(ev: MatCheckboxChange)

Toggles the row selection state.

Parameters :
Name Type Optional
ev MatCheckboxChange No
Returns : void
triggerColumnHeaderTooltip
triggerColumnHeaderTooltip(event: FocusOrigin, tooltip: MatTooltip)
Parameters :
Name Type Optional
event FocusOrigin No
tooltip MatTooltip No
Returns : void

Properties

areFilersCollapsed$
Type : Observable<boolean>
columns$
Default value : new BehaviorSubject<UiGridColumnDirective<T>[]>([])

Emits the column definitions when their definition changes.

dataManager
Default value : new DataManager<T>(this._gridOptions)

Data manager, used to optimize row rendering.

Optional displayToggleColumnsDivider$
Type : Observable<boolean>

Emits with information whether the dvider for toggle columns should be displayed

filterManager
Default value : new FilterManager<T>()

Filter manager, used to manage filter state changes.

focusedColumnHeader
Default value : false

Whether column header is focused.

hasAnyFiltersVisible$
Type : Observable<boolean>

Emits with information whether any filter is visible.

hasSelection$
Default value : this.selectionManager.hasValue$.pipe( tap(hasSelection => { if (hasSelection && !!this.header?.actionButtons?.length) { this._announceGridHeaderActions(); } }), share(), )
isAnyFilterDefined$
Default value : new BehaviorSubject<boolean>(false)

Emits with information whether filters are defined.

Optional liveAnnouncerManager
Type : LiveAnnouncerManager<T>

Live announcer manager, used to emit notification via aria-live.

renderedColumns$
Default value : this.visible$.pipe( map(columns => { const firstIndex = columns.findIndex(c => c.primary); const rowHeaderIndex = firstIndex > -1 ? firstIndex : 0; return columns.map((directive, index) => ({ directive, role: index === rowHeaderIndex ? 'rowheader' : 'gridcell', })); }), )
resizeManager
Type : ResizeManager<T>

Resize manager, used to compute resized column states.

scrollCompensationWidth
Type : number
Default value : 0

Returns the scroll size, in order to compensate for the scrollbar.

selectionManager
Default value : new SelectionManager<T>()

Selection manager, used to manage grid selection states.

showFilters
Default value : false

Toggle filters row display state

sortManager
Default value : new SortManager<T>()

Sort manager, used to manage sort state changes.

visibilityManager
Default value : new VisibilityManger<T>()

Visibility manager, used to manage visibility of columns.

visible$
Default value : this.visibilityManager.columns$

Emits the visible column definitions when their definition changes.

visibleColumnsToggle$
Default value : new BehaviorSubject<boolean>(false)

Emits when the visible columns menu has been opened or closed

Accessors

data
setdata(value: T[] | null)

The data list that needs to be rendered within the grid.

NOTE: to have access to all functionality, we recommend that entities display in the grid implement the IGridDataEntry interface.

Parameters :
Name Type Optional Description
value T[] | null No

The list that needs to rendered.

Returns : void
isResizing
getisResizing()

Marks the grid resizing state.

isEveryVisibleRowChecked
getisEveryVisibleRowChecked()

Determines if all of the items are currently checked.

hasValueOnVisiblePage
gethasValueOnVisiblePage()

Determines if there's a value selected within the currently rendered items (used for multi-page selection).

resizeStrategy
setresizeStrategy(value: ResizeStrategy)

The desired resize strategy.

FIXME: Currently only ImmediateNeighbourHalt is stable.

Parameters :
Name Type Optional
value ResizeStrategy No
Returns : void
collapseFiltersCount
getcollapseFiltersCount()
setcollapseFiltersCount(count: number)

Configure if the grid search filters are eager or on open.

Parameters :
Name Type Optional
count number No
Returns : void
fetchStrategy
getfetchStrategy()
setfetchStrategy(fetchStrategy: "eager" | "onOpen")

Configure if the grid search filters are eager or on open.

Parameters :
Name Type Optional
fetchStrategy "eager" | "onOpen" No
Returns : void
collapsibleFilters
getcollapsibleFilters()
setcollapsibleFilters(collapse: boolean)

Option to have collapsible filters.

Parameters :
Name Type Optional
collapse boolean No
Returns : void
expandedEntry
getexpandedEntry()
setexpandedEntry(entry: T | undefined)

Set the expanded entry.

Parameters :
Name Type Optional
entry T | undefined No
Returns : void
expandedEntries
getexpandedEntries()
setexpandedEntries(entry: T | T[] | undefined)

Set the expanded entry / entries.

Parameters :
Name Type Optional
entry T | T[] | undefined No
Returns : void
customFilterValue
setcustomFilterValue(customValue: IFilterModel<T>[])
Parameters :
Name Type Optional
customValue IFilterModel<T>[] No
Returns : void
showMultiPageSelectionInfo
getshowMultiPageSelectionInfo()

Determines if the multi-page selection row should be displayed.

import range from 'lodash-es/range';
import {
    animationFrameScheduler,
    BehaviorSubject,
    combineLatest,
    merge,
    Observable,
    of,
    Subject,
    Subscription,
} from 'rxjs';
import {
    debounceTime,
    distinctUntilChanged,
    filter,
    map,
    observeOn,
    share,
    shareReplay,
    startWith,
    switchMap,
    take,
    takeUntil,
    tap,
} from 'rxjs/operators';

import {
    animate,
    style,
    transition,
    trigger,
} from '@angular/animations';
import { FocusOrigin } from '@angular/cdk/a11y';
import {
    AfterContentInit,
    ChangeDetectionStrategy,
    ChangeDetectorRef,
    Component,
    ContentChild,
    ContentChildren,
    ElementRef,
    EventEmitter,
    HostBinding,
    Inject,
    InjectionToken,
    Input,
    NgZone,
    OnChanges,
    OnDestroy,
    Optional,
    Output,
    QueryList,
    SimpleChanges,
    ViewChild,
    ViewEncapsulation,
} from '@angular/core';
import {
    MatCheckbox,
    MatCheckboxChange,
} from '@angular/material/checkbox';
import { MatTooltip } from '@angular/material/tooltip';
import { QueuedAnnouncer } from '@uipath/angular/a11y';
import { ISuggestValue } from '@uipath/angular/components/ui-suggest';

import { UiGridColumnDirective } from './body/ui-grid-column.directive';
import { UiGridExpandedRowDirective } from './body/ui-grid-expanded-row.directive';
import { UiGridLoadingDirective } from './body/ui-grid-loading.directive';
import { UiGridNoContentDirective } from './body/ui-grid-no-content.directive';
import { UiGridRowActionDirective } from './body/ui-grid-row-action.directive';
import { UiGridRowCardViewDirective } from './body/ui-grid-row-card-view.directive';
import { UiGridRowConfigDirective } from './body/ui-grid-row-config.directive';
import { UiGridSearchFilterDirective } from './filters/ui-grid-search-filter.directive';
import { UiGridFooterDirective } from './footer/ui-grid-footer.directive';
import { UiGridHeaderDirective } from './header/ui-grid-header.directive';
import {
    DataManager,
    FilterManager,
    LiveAnnouncerManager,
    PerformanceMonitor,
    ResizeManager,
    ResizeManagerFactory,
    ResizeStrategy,
    SelectionManager,
    SortManager,
    VisibilityManger,
} from './managers';
import { ResizableGrid } from './managers/resize/types';
import {
    GridOptions,
    IFilterModel,
    IGridDataEntry,
    ISortModel,
} from './models';
import { UiGridIntl } from './ui-grid.intl';

export const UI_GRID_OPTIONS = new InjectionToken<GridOptions<unknown>>('UiGrid DataManager options.');
const DEFAULT_VIRTUAL_SCROLL_ITEM_SIZE = 48;
const FOCUSABLE_ELEMENTS_QUERY = 'a, button:not([hidden]), input:not([hidden]), textarea, select, details, [tabindex]:not([tabindex="-1"])';

@Component({
    selector: 'ui-grid',
    templateUrl: './ui-grid.component.html',
    styleUrls: [
        './ui-grid.component.scss',
    ],
    animations: [
        trigger('filters-container', [
            transition(':enter', [
                style({
                    minHeight: '0',
                    height: '0',
                    opacity: '0',
                }),
                animate('0.15s ease-in', style({
                    opacity: '*',
                    minHeight: '*',
                    height: '*',
                    display: '*',
                })),
            ]),
            transition(':leave', [
                style({
                    minHeight: '*',
                    height: '*',
                }),
                animate('0.15s ease-in', style({
                    opacity: '0',
                    minHeight: '0',
                    height: '0',
                })),
            ]),
        ]),
    ],
    changeDetection: ChangeDetectionStrategy.OnPush,
    encapsulation: ViewEncapsulation.None,
})
export class UiGridComponent<T extends IGridDataEntry> extends ResizableGrid<T> implements AfterContentInit, OnChanges, OnDestroy {
    /**
     * The data list that needs to be rendered within the grid.
     *
     * NOTE: to have access to all functionality, we recommend that entities display in the grid implement the IGridDataEntry interface.
     *
     * @param value The list that needs to rendered.
     */
    @Input()
    set data(value: T[] | null) {
        this._performanceMonitor.reset();
        this.dataManager.update(value);
    }

    /**
     * Marks the grid resizing state.
     *
     */
    @HostBinding('class.ui-grid-state-resizing')
    @Input()
    get isResizing() {
        return this.resizeManager.isResizing;
    }

    /**
     * Marks the grid projected state.
     *
     */
    @HostBinding('class.ui-grid-state-projected')
    @Input()
    isProjected: boolean;

    /**
     * Determines if all of the items are currently checked.
     *
     */
    get isEveryVisibleRowChecked() {
        return !!this.dataManager.length &&
            this.dataManager.every(row => this.selectionManager.isSelected(row!));
    }

    /**
     * Determines if there's a value selected within the currently rendered items (used for multi-page selection).
     *
     */
    get hasValueOnVisiblePage() {
        return this.dataManager.some(row => this.selectionManager.isSelected(row!));
    }

    /**
     * The desired resize strategy.
     *
     * FIXME: Currently only `ImmediateNeighbourHalt` is stable.
     *
     */
    @Input()
    set resizeStrategy(value: ResizeStrategy) {
        if (value === this._resizeStrategy) { return; }

        this._resizeStrategy = value;

        if (this.resizeManager != null) {
            this.resizeManager.destroy();
        }

        this._initResizeManager();
    }

    /**
     * Marks the grid loading state.
     *
     */
    @HostBinding('class.ui-grid-state-loading')
    @Input()
    loading = false;

    /**
     * Marks the grid enabled state.
     *
     */
    @HostBinding('class.ui-grid-state-disabled')
    @Input()
    disabled = false;

    /**
     * Configure if the grid search filters are eager or on open.
     *
     */
    @Input()
    set collapseFiltersCount(count: number) {
        if (count === this._collapseFiltersCount$.value) { return; }
        this._collapseFiltersCount$.next(count);
    }
    get collapseFiltersCount() {
        return this._collapseFiltersCount$.value;
    }

    /**
     * Configure if the grid search filters are eager or on open.
     *
     */
    @Input()
    set fetchStrategy(fetchStrategy: 'eager' | 'onOpen') {
        if (fetchStrategy === this.fetchStrategy) { return; }
        this._fetchStrategy = fetchStrategy;
    }
    get fetchStrategy() {
        return this._fetchStrategy;
    }

    /**
     * Configure if the grid allows item selection.
     *
     */
    @Input()
    selectable = true;

    /**
     * Option to select an alternate layout for footer pagination.
     *
     */
    @Input()
    useLegacyDesign: boolean;

    /**
     * Option to have collapsible filters.
     *
     * @deprecated - use `[collapseFiltersCount]="0" to render collapsed or leave out to always render inline`
     */
    @Input()
    set collapsibleFilters(collapse: boolean) {
        this._collapseFiltersCount$.next(collapse ? 0 : Number.POSITIVE_INFINITY);
    }
    get collapsibleFilters() {
        return !this._collapseFiltersCount$.value;
    }

    /**
     * Configure if the grid allows to toggle column visibility.
     *
     */
    @Input()
    toggleColumns = false;

    /**
     * Configure if the grid allows multi-page selection.
     *
     */
    @HostBinding('class.ui-grid-mode-multi-select')
    @Input()
    multiPageSelect = false;

    /**
     * Configure if the grid is refreshable.
     *
     */
    @Input()
    refreshable = true;

    /**
     * Configure if `virtualScroll` is enabled.
     *
     */
    @Input()
    virtualScroll = false;

    /**
     * Configure the row item size for virtualScroll
     *
     */
    @Input()
    rowSize: number;

    /**
     * Show paint time stats
     *
     */
    @Input()
    showPaintTime = false;

    /**
     * Provide a custom `noDataMessage`.
     *
     */
    @Input()
    noDataMessage?: string;

    /**
     * Set the expanded entry.
     *
     * @deprecated Use `expandedEntries` instead.
     */
    @Input()
    set expandedEntry(entry: T | undefined) {
        this.expandedEntries = entry;
    }
    get expandedEntry() {
        return this._expandedEntries[0];
    }

    /**
     * Set the expanded entry / entries.
     *
     */
    @Input()
    set expandedEntries(entry: T | T[] | undefined) {
        if (!entry) {
            this._expandedEntries = [];
            return;
        }
        this._expandedEntries = Array.isArray(entry) ? entry : [entry];
    }
    get expandedEntries() {
        return this._expandedEntries;
    }

    /**
     * Configure if the expanded entry should replace the active row, or add a new row with the expanded view.
     *
     */
    @Input()
    expandMode: 'preserve' | 'collapse' = 'collapse';

    /**
     * Configure if ui-grid-header-row should be visible, by default it is visible
     *
     */
    @Input()
    showHeaderRow = true;

    /**
     * Configure a function that receives the whole grid row, and returns
     * disabled message if the row should not be selectable
     *
     */
    @Input()
    disableSelectionByEntry: (entry: T) => null | string;

    @Input()
    set customFilterValue(customValue: IFilterModel<T>[]) {
        if (!Array.isArray(customValue) || !customValue.length) { return; }
        this.filterManager.updateCustomFilters(customValue);
    }

    /**
     * Configure if Card view should be used
     *
     */
    @Input()
    useCardView = false;

    /**
     * Emits an event with the sort model when a column sort changes.
     *
     */
    @Output()
    sortChange = new EventEmitter<ISortModel<T>>();

    /**
     * Emits an event when user click the refresh button.
     *
     */
    @Output()
    refresh = new EventEmitter<void>();

    /**
     * Emits an event once the grid has been rendered.
     *
     */
    @Output()
    rendered = new EventEmitter<void>();

    /**
     * Emits an event once the grid has been rendered.
     *
     */
    @Output()
    resizeEnd = new EventEmitter<void>();

    @Output()
    removeCustomFilter = new EventEmitter<void>();

    /**
     * Emits an event when a row is clicked.
     *
     */
    @Output()
    rowClick = new EventEmitter<{ event: Event; row: T }>();

    /**
     * Emits the column definitions when their definition changes.
     *
     */
    columns$ = new BehaviorSubject<UiGridColumnDirective<T>[]>([]);

    /**
     * Row configuration directive reference.
     *
     * @ignore
     */
    @ContentChild(UiGridRowConfigDirective, {
        static: true,
    })
    rowConfig?: UiGridRowConfigDirective<T>;

    /**
     * Row action directive reference.
     *
     * @ignore
     */
    @ContentChild(UiGridRowActionDirective, {
        static: true,
    })
    actions?: UiGridRowActionDirective;

    /**
     * Footer directive reference.
     *
     * @ignore
     */
    @ContentChild(UiGridFooterDirective, {
        static: true,
    })
    footer?: UiGridFooterDirective;

    /**
     * Header directive reference.
     *
     * @ignore
     */
    @ContentChild(UiGridHeaderDirective, {
        static: true,
    })
    header?: UiGridHeaderDirective<T>;

    /**
     * Column directive reference list.
     *
     * @ignore
     */
    @ContentChildren(UiGridColumnDirective)
    columns!: QueryList<UiGridColumnDirective<T>>;

    /**
     * Expanded row template reference.
     *
     * @ignore
     */
    @ContentChild(UiGridExpandedRowDirective, {
        static: true,
    })
    expandedRow?: UiGridExpandedRowDirective;

    /**
     * No content custom template reference.
     *
     * @ignore
     */
    @ContentChild(UiGridNoContentDirective, {
        static: true,
    })
    noContent?: UiGridNoContentDirective;

    /**
     * Custom loading template reference.
     *
     * @ignore
     */
    @ContentChild(UiGridLoadingDirective, {
        static: true,
    })
    loadingState?: UiGridLoadingDirective;

    /**
     * Custom card view template reference.
     *
     * @ignore
     */
    @ContentChild(UiGridRowCardViewDirective, {
        static: true,
    })
    cardTemplate?: UiGridRowCardViewDirective<T>;
    /**
     * Reference to the grid action buttons container
     *
     * @ignore
     */
    @ViewChild('gridActionButtons')
    gridActionButtons!: ElementRef;

    /**
     * Reference to select all available rows checkbox
     *
     * @ignore
     */
    @ViewChild('selectAvailableRowsCheckbox')
    selectAvailableRowsCheckbox?: MatCheckbox;

    /**
     * Toggle filters row display state
     *
     */
    showFilters = false;

    /**
     * Live announcer manager, used to emit notification via `aria-live`.
     *
     */
    liveAnnouncerManager?: LiveAnnouncerManager<T>;

    /**
     * Selection manager, used to manage grid selection states.
     *
     */
    selectionManager = new SelectionManager<T>();

    /**
     * Data manager, used to optimize row rendering.
     *
     */
    dataManager = new DataManager<T>(this._gridOptions);

    /**
     * Filter manager, used to manage filter state changes.
     *
     */
    filterManager = new FilterManager<T>();

    /**
     * Visibility manager, used to manage visibility of columns.
     *
     */
    visibilityManager = new VisibilityManger<T>();

    /**
     * Sort manager, used to manage sort state changes.
     *
     */
    sortManager = new SortManager<T>();

    /**
     * Resize manager, used to compute resized column states.
     *
     */
    resizeManager!: ResizeManager<T>;

    /**
     * @ignore
     */
    paintTime$: Observable<string>;

    /**
     * Emits with information whether filters are defined.
     *
     */
    isAnyFilterDefined$ = new BehaviorSubject<boolean>(false);

    /**
     * Emits with information whether any filter is visible.
     *
     */
    hasAnyFiltersVisible$: Observable<boolean>;

    /**
     * Emits with information whether the dvider for toggle columns should be displayed
     *
     */
    displayToggleColumnsDivider$?: Observable<boolean>;

    /**
     * Emits the visible column definitions when their definition changes.
     *
     */
    visible$ = this.visibilityManager.columns$;

    /**
     * Emits when the visible columns menu has been opened or closed
     *
     */
    visibleColumnsToggle$ = new BehaviorSubject<boolean>(false);

    /**
     * Returns the scroll size, in order to compensate for the scrollbar.
     *
     * @deprecated
     */
    scrollCompensationWidth = 0;

    /**
     * Whether column header is focused.
     *
     */
    focusedColumnHeader = false;

    /**
     * @internal
     * @ignore
     */
    scrollCompensationWidth$ = this.dataManager.data$.pipe(
        map(data => data.length),
        distinctUntilChanged(),
        observeOn(animationFrameScheduler),
        debounceTime(0),
        map(() => this._ref.nativeElement.querySelector('.ui-grid-viewport')),
        map(view => view ? view.offsetWidth - view.clientWidth : 0),
        // eslint-disable-next-line import/no-deprecated
        tap(compensationWidth => this.scrollCompensationWidth = compensationWidth),
    );

    hasSelection$ = this.selectionManager.hasValue$.pipe(
        tap(hasSelection => {
            if (hasSelection && !!this.header?.actionButtons?.length) {
                this._announceGridHeaderActions();
            }
        }),
        share(),
    );

    renderedColumns$ = this.visible$.pipe(
        map(columns => {
            const firstIndex = columns.findIndex(c => c.primary);
            const rowHeaderIndex = firstIndex > -1 ? firstIndex : 0;

            return columns.map((directive, index) => ({
                directive,
                role: index === rowHeaderIndex ? 'rowheader' : 'gridcell',
            }));
        }),
    );

    areFilersCollapsed$: Observable<boolean>;

    /**
     * Determines if the multi-page selection row should be displayed.
     *
     */
    get showMultiPageSelectionInfo() {
        return this.multiPageSelect &&
            !this.dataManager.pristine &&
            (
                this.dataManager.length ||
                this.selectionManager.selected.length
            );
    }

    protected _destroyed$ = new Subject<void>();
    protected _columnChanges$: Observable<SimpleChanges>;

    private _fetchStrategy!: 'eager' | 'onOpen';
    private _collapseFiltersCount$!: BehaviorSubject<number>;
    private _resizeStrategy = ResizeStrategy.ImmediateNeighbourHalt;
    private _performanceMonitor: PerformanceMonitor;
    private _configure$ = new Subject<void>();
    private _isShiftPressed = false;
    private _lastCheckboxIdx = 0;
    private _resizeSubscription$: null | Subscription = null;
    private _expandedEntries: T[] = [];
    /**
     * @ignore
     */
    constructor(
        @Optional()
        public intl: UiGridIntl,
        protected _ref: ElementRef,
        protected _cd: ChangeDetectorRef,
        private _zone: NgZone,
        private _queuedAnnouncer: QueuedAnnouncer,
        @Inject(UI_GRID_OPTIONS)
        @Optional()
        private _gridOptions?: GridOptions<T>,
    ) {
        super();

        this.disableSelectionByEntry = () => null;
        this.useLegacyDesign = _gridOptions?.useLegacyDesign ?? false;
        this._fetchStrategy = _gridOptions?.fetchStrategy ?? 'onOpen';
        this.rowSize = _gridOptions?.rowSize ?? DEFAULT_VIRTUAL_SCROLL_ITEM_SIZE;
        this._collapseFiltersCount$ = new BehaviorSubject(
            _gridOptions?.collapseFiltersCount ?? (_gridOptions?.collapsibleFilters === true ? 0 : Number.POSITIVE_INFINITY),
        );

        this.isProjected = this._ref.nativeElement.classList.contains('ui-grid-state-responsive');

        this.intl = intl || new UiGridIntl();

        this._columnChanges$ =
            this.rendered.pipe(
                switchMap(() => merge(
                    ...this.columns.map(column =>
                        column.change$,
                    )),
                ),
                debounceTime(10),
                tap(() => this.isResizing && this.resizeManager.stop()),
            );

        const visibleFilterCount$ = this.rendered.pipe(
            switchMap(() => this.columns.changes),
            startWith('Initial emission'),
            switchMap(() =>
                combineLatest(this.columns.map((column: UiGridColumnDirective<T>) =>
                    column.dropdown?.visible$ ?? column.searchableDropdown?.visible$ ?? of(false),
                )),
            ),
            map(areVisible => areVisible.filter(visible => visible).length),
            distinctUntilChanged(),
            shareReplay(),
        );

        this.hasAnyFiltersVisible$ = visibleFilterCount$.pipe(
            map(Boolean),
            distinctUntilChanged(),
        );

        this.areFilersCollapsed$ = combineLatest([
            visibleFilterCount$,
            this._collapseFiltersCount$,
        ]).pipe(
            map(([visible, minCollapse]) => visible > minCollapse),
            distinctUntilChanged(),
        );

        const sort$ = this.sortManager
            .sort$
            .pipe(
                tap(ev => this.sortChange.emit(ev)),
            );

        const inputChanges$ = merge(
            this.intl.changes,
            this._configure$,
            this._columnChanges$,
        ).pipe(
            map(() => this.columns.toArray()),
            tap(columns => this.filterManager.columns = columns),
            tap(columns => this.sortManager.columns = columns),
            tap(columns => this.visibilityManager.columns = columns),
            tap(columns => this.columns$.next(columns)),
            tap(columns => this.isAnyFilterDefined$.next(
                columns.some(c => !!c.dropdown || !!c.searchableDropdown),
            )),
        );

        const data$ = this.dataManager.data$.pipe(
            tap(_ => this._lastCheckboxIdx = 0),
        );

        const selection$ = this.selectionManager.changed$.pipe(
            tap(_ => this._cd.markForCheck()),
        );

        merge(
            sort$,
            inputChanges$,
            data$,
            selection$,
        ).pipe(
            takeUntil(this._destroyed$),
        ).subscribe();

        this._initResizeManager();
        this._performanceMonitor = new PerformanceMonitor(_ref.nativeElement);
        this.paintTime$ = this._performanceMonitor.paintTime$;

        this.selectionManager.hasValue$.pipe(
            filter(hasValue => !hasValue && this.selectAvailableRowsCheckbox?.checked === true),
            takeUntil(this._destroyed$),
        ).subscribe(() => this.selectAvailableRowsCheckbox!.checked = false);

        this._initDisplayToggleColumnsDivider();
    }

    /**
     * Clear search term, filters and sorting and emits true after.
     */
    @Input()
    reset: () => Observable<boolean> = () => {
        if (this.header) {
            this.header.searchValue = '';
        }
        this.filterManager.clear();
        this.sortManager.clear();
        return of(true);
    };

    /**
     * @ignore
     */
    ngAfterContentInit() {
        this.selectionManager.disableSelectionByEntry = this.disableSelectionByEntry;

        this.liveAnnouncerManager = new LiveAnnouncerManager(
            msg => this._queuedAnnouncer.enqueue(msg),
            this.intl,
            this.dataManager.data$,
            this.sortManager.sort$.pipe(
                filter(({ userEvent }) => !!userEvent),
            ),
            this.refresh,
            this.footer?.pageChange,
        );

        this._configure$.next();

        this._zone.onStable.pipe(
            take(1),
        ).subscribe(() => {
            // ensure everything is painted once initial rendering is done
            // a lot of templates loaded lazily, this is required
            // to ensure everything is drawn once the grid is initalized
            this._cd.markForCheck();

            this.rendered.next();
        });

        this.columns.changes
            .pipe(
                takeUntil(this._destroyed$),
            ).subscribe(
                () => this._configure$.next(),
            );
    }

    /**
     * @ignore
     */
    ngOnChanges(changes: SimpleChanges) {
        const selectableChange = changes.selectable;
        if (
            selectableChange &&
            !selectableChange.firstChange &&
            selectableChange.previousValue !== selectableChange.currentValue
        ) {
            this.selectionManager.clear();
            this._configure$.next();
        }

        const dataChange = changes.data;

        if (
            dataChange &&
            !dataChange.firstChange &&
            !this.multiPageSelect
        ) {
            this._performanceMonitor.reset();
            this.selectionManager.clear();
        }
    }

    /**
     * @ignore
     */
    ngOnDestroy() {
        this.sortChange.complete();
        this.rendered.complete();
        this.columns$.complete();
        this.isAnyFilterDefined$.complete();

        this.dataManager.destroy();
        this.resizeManager.destroy();
        this.sortManager.destroy();
        this.selectionManager.destroy();
        this.filterManager.destroy();
        this.visibilityManager.destroy();

        if (this.liveAnnouncerManager) {
            this.liveAnnouncerManager.destroy();
        }

        this._performanceMonitor.destroy();

        this._destroyed$.next();
        this._destroyed$.complete();
        this._configure$.complete();
    }

    /**
     * Marks if the `Shift` key is pressed.
     */
    checkShift(event: Event) {
        event.stopPropagation();

        this._isShiftPressed = (event as MouseEvent | KeyboardEvent).shiftKey;
    }

    /**
     * Handles row selection, and reacts if the `Shift` key is pressed.
     *
     * @param idx The clicked row index.
     * @param entry The entry associated to the selected row.
     */
    handleSelection(idx: number, entry: T) {
        if (!this._isShiftPressed) {
            this._lastCheckboxIdx = idx;
            this.selectionManager.toggle(entry);
            return;
        }

        const min = Math.min(this._lastCheckboxIdx, idx);
        const max = Math.max(idx, this._lastCheckboxIdx);

        const rowsForSelection = range(min, max + 1)
            .map(this.dataManager.get);
        const rowsForDeselection = this.dataManager.data$.getValue()
            .filter(row => !rowsForSelection.find(rowForSelection => rowForSelection.id === row.id));

        /**
         * To be consistent with the browser, if we click on a row
         * that was already selected, we unselect it, sync with DOM (detectChanges),
         * then we select it again (it's included in rowsForSelection).
         */
        if (this.selectionManager.isSelected(entry)) {
            this.selectionManager.deselect(entry);
            this._cd.detectChanges();
        }

        this.selectionManager.select(...rowsForSelection.filter(row => !this.selectionManager.isSelected(row)));
        this.selectionManager.deselect(...rowsForDeselection.filter(row => this.selectionManager.isSelected(row)));

        this._cd.detectChanges();
    }

    /**
     * Toggles the row selection state.
     *
     */
    toggle(ev: MatCheckboxChange) {
        if (ev.checked) {
            this.dataManager.forEach(row => this.selectionManager.select(row!));
        } else {
            this._lastCheckboxIdx = 0;
            this.dataManager.forEach(row => this.selectionManager.deselect(row!));
        }
    }

    /**
     * Determines the `checkbox` `matToolTip`.
     *
     * @param [row] The row for which the label is computed.
     */
    checkboxTooltip(row?: T): string {
        if (!row) {
            return this.intl.checkboxTooltip(this.isEveryVisibleRowChecked);
        }

        return this.intl.checkboxTooltip(this.selectionManager.isSelected(row), this.dataManager.indexOf(row));
    }

    /**
     * Determines the `checkbox` aria-label`.
     * **DEPRECATED**
     *
     * @param [row] The row for which the label is computed.
     */
    checkboxLabel(row?: T): string {
        if (!row) {
            return `${this.isEveryVisibleRowChecked ? 'select' : 'deselect'} all`;
        }
        return `${this.selectionManager.isSelected(row) ? 'deselect' : 'select'} row ${this.dataManager.indexOf(row)}`;
    }

    focusRowHeader() {
        this.gridActionButtons?.nativeElement.querySelector(FOCUSABLE_ELEMENTS_QUERY)?.focus();
    }

    clearCustomFilter() {
        this.removeCustomFilter.emit();
        this.filterManager.clearCustomFilters();
    }

    isRowExpanded(rowId?: IGridDataEntry['id']) {
        if (rowId == null) {
            return false;
        }

        return this._expandedEntries.some(el => el.id === rowId);
    }

    onRowClick(event: Event, row: T) {
        this.rowClick.emit({
            event,
            row,
        });
    }

    checkIndeterminateState(indeterminateState: boolean) {
        // If the grid has disabled rows the indeterminate can be set to false and still not have all the rows selected,
        // in that case we set the indeterminate to true
        if (
            !indeterminateState &&
            this.selectAvailableRowsCheckbox &&
            this.hasValueOnVisiblePage &&
            !this.isEveryVisibleRowChecked
        ) {
            this.selectAvailableRowsCheckbox.indeterminate = true;
        }
    }

    searchableDropdownValue(searchableDropdown: UiGridSearchFilterDirective<T>): ISuggestValue[] {
        if (searchableDropdown.value) {
            if (searchableDropdown.multiple) {
                return searchableDropdown.value as ISuggestValue[];
            }
            return [searchableDropdown.value as ISuggestValue];
        }
        return [];
    }

    getColumnName(column: UiGridColumnDirective<T>, prefix = 'ui-grid-dropdown-filter') {
        return prefix + '-' + ((column.property as string) ?? 'na');
    }

    isFilterApplied(column: UiGridColumnDirective<T>) {
        return (column.dropdown?.value != null && column.dropdown!.value!.value !== column.dropdown!.emptyStateValue)
            || (column.searchableDropdown?.value != null && (column.searchableDropdown?.value as ISuggestValue[])?.length !== 0);
    }

    triggerColumnHeaderTooltip(event: FocusOrigin, tooltip: MatTooltip) {
        if (event === 'keyboard') {
            this.focusedColumnHeader = true;
            tooltip.show();
        }
    }

    hideColumnHeaderTooltip(tooltip: MatTooltip) {
        tooltip.hide();
        this.focusedColumnHeader = false;
    }

    private _announceGridHeaderActions() {
        this._queuedAnnouncer.enqueue(this.intl.gridHeaderActionsNotice);
    }

    private _initResizeManager() {
        this._resizeSubscription$?.unsubscribe();
        this.resizeManager = ResizeManagerFactory(this._resizeStrategy, this);
        this._resizeSubscription$ = this.resizeManager.resizeEnd$.subscribe(() => this.resizeEnd.emit());
    }

    private _initDisplayToggleColumnsDivider() {
        this.displayToggleColumnsDivider$ = combineLatest([this.hasAnyFiltersVisible$, this.filterManager.hasCustomFilter$]).pipe(
            map(([hasAnyFilterVisible, hasCustomFilters]) => hasAnyFilterVisible || hasCustomFilters),
        );
    }

}
<ng-container *ngIf="showPaintTime && (paintTime$ | async) as paintTime">
    <div class="ui-grid-debug-information">
        Painted {{ dataManager.length }} rows in {{ paintTime }}ms
    </div>
</ng-container>

<div *ngIf="toggleColumns ||
            header?.mainButtons?.length ||
            header?.actionButtons?.length ||
            header?.inlineButtons?.length ||
            header?.search ||
            (isAnyFilterDefined$ | async)"
     class="ui-grid-filter-container">
    <div class="ui-grid-filter-container-lhs-group">
        <div class="ui-grid-filter-container-lhs-group-actions">
            <ng-container *ngIf="filterManager.hasCustomFilter$ | async; else noCustomFilter">
                <ng-container *ngTemplateOutlet="toggleColumnsTmpl"></ng-container>
                <button *ngIf="!(hasSelection$ | async)"
                        (click)="clearCustomFilter()"
                        mat-flat-button
                        type="button"
                        data-cy="clear-custom-filter">
                    {{ intl.clearCustomFilter }}
                </button>
            </ng-container>
            <ng-template #noCustomFilter>
                <ng-container>
                    <ng-container *ngIf="useLegacyDesign">
                        <ng-container *ngTemplateOutlet="toggleColumnsTmpl"></ng-container>
                    </ng-container>

                    <ng-container *ngIf="!(hasSelection$ | async) ||
                            !header?.actionButtons?.length">
                        <ui-grid-search *ngIf="header?.search"
                                        [debounce]="header!.searchDebounce"
                                        [maxLength]="header!.searchMaxLength"
                                        [placeholder]="intl.searchPlaceholder"
                                        [searchTooltip]="intl.searchTooltip"
                                        [clearTooltip]="intl.clearTooltip"
                                        [tooltipDisabled]="resizeManager.isResizing"
                                        [value]="header!.searchValue!"
                                        (searchChange)="filterManager.searchChange($event, header!, footer)"
                                        class="ui-grid-search ui-grid-filter-option">
                        </ui-grid-search>

                        <ng-container *ngIf="!useLegacyDesign">
                            <ng-container *ngTemplateOutlet="toggleColumnsTmpl"></ng-container>
                        </ng-container>

                        <ng-container *ngTemplateOutlet="filtersTmpl">
                        </ng-container>
                    </ng-container>

                    <div *ngIf="!(hasSelection$ | async)"
                         class="ui-grid-action-buttons ui-grid-action-buttons-inline">
                        <ng-container *ngFor="let button of header?.inlineButtons">
                            <ng-container *ngIf="button.visible">
                                <ng-container *ngTemplateOutlet="button.html ?? null">
                                </ng-container>
                            </ng-container>
                        </ng-container>
                    </div>
                </ng-container>
            </ng-template>

            <div #gridActionButtons
                 *ngIf="hasSelection$ | async"
                 class="ui-grid-action-buttons ui-grid-action-buttons-selection">
                <ng-container *ngFor="let button of header?.actionButtons">
                    <ng-container *ngIf="button.visible">
                        <ng-container *ngTemplateOutlet="button.html?? null">
                        </ng-container>
                    </ng-container>
                </ng-container>
            </div>
        </div>
        <div *ngIf="showFilters &&
             (hasAnyFiltersVisible$ | async) &&
             (filterManager.hasCustomFilter$ | async) === false &&
             !(hasSelection$ | async)"
             [@filters-container]
             class="ui-grid-filter-container-lhs-group-filters">
            <ng-container *ngTemplateOutlet="inlineFiltersTmpl"></ng-container>
        </div>
    </div>

    <div class="ui-grid-filter-container-rhs-group">
        <div *ngIf="!useLegacyDesign || header?.mainButtons?.length ?? 0 > 1"
             class="ui-grid-action-buttons ui-grid-action-buttons-main">
            <ng-container *ngFor="let button of header?.mainButtons">
                <ng-container *ngIf="button.visible">
                    <ng-container *ngTemplateOutlet="button.html ?? null">
                    </ng-container>
                </ng-container>
            </ng-container>
        </div>
    </div>
</div>

<ng-container *ngIf="!useLegacyDesign && multiPageSelect then multiPageSelectionRow">
</ng-container>

<div [class.use-alternate-design]="!useLegacyDesign"
     (keydown.shift.alt.arrowup)="focusRowHeader()"
     class="ui-grid-container">

    <div class="ui-grid-table-container">
        <div *ngIf="useLegacyDesign && header?.mainButtons?.length === 1"
             class="ui-grid-action-button">
            <ng-container *ngIf="header!.mainButtons![0] as mainBtn">
                <ng-container *ngIf="mainBtn.visible">
                    <ng-container *ngTemplateOutlet="mainBtn.html ?? null">
                    </ng-container>
                </ng-container>
            </ng-container>
        </div>

        <div [class.ui-grid-table-refreshable]="refreshable"
             class="ui-grid-table"
             role="grid">
            <div class="ui-grid-header">
                <div *ngIf="showHeaderRow"
                     class="ui-grid-header-row"
                     role="row">
                    <div *ngIf="selectable"
                         class="ui-grid-header-cell ui-grid-checkbox-cell ui-grid-feature-cell"
                         role="columnheader">
                        <mat-checkbox #selectAvailableRowsCheckbox
                                      (change)="$event && toggle($event)"
                                      (indeterminateChange)="checkIndeterminateState($event)"
                                      [disabled]="!dataManager.length"
                                      [checked]="isEveryVisibleRowChecked"
                                      [indeterminate]="hasValueOnVisiblePage && !isEveryVisibleRowChecked"
                                      [matTooltip]="checkboxTooltip()"
                                      [aria-label]="checkboxTooltip()"
                                      tabindex="0">
                        </mat-checkbox>
                    </div>

                    <ng-container *ngIf="multiPageSelect then multiPageSelectionProjectedHeaderCell">
                    </ng-container>
                    <div *ngFor="let column of visible$ | async; let last = last; let columnIndex = index"
                         [class.ui-grid-header-cell-sortable]="column.sortable"
                         [class.ui-grid-resizeable]="column.resizeable"
                         [class.ui-grid-state-resizing]="column === resizeManager.current?.column"
                         [attr.aria-sort]="column.ariaSort"
                         [attr.data-property]="column.property"
                         [attr.data-identifier]="column.identifier"
                         [style.width.%]="column.width"
                         (cdkFocusChange)="triggerColumnHeaderTooltip($event, columnTooltip)"
                         (focusout)="hideColumnHeaderTooltip(columnTooltip)"
                         (keyup.enter)="sortManager.changeSort(column)"
                         (keyup.space)="sortManager.changeSort(column)"
                         (keydown.ArrowLeft)="!last && resizeManager.startKeyboardResize('left', column, columnIndex)"
                         (keydown.ArrowRight)="!last && resizeManager.startKeyboardResize('right', column, columnIndex)"
                         cdkMonitorElementFocus
                         class="ui-grid-header-cell"
                         role="columnheader"
                         tabindex="0">
                        <div (click)="sortManager.changeSort(column)"
                             class="ui-grid-header-title">
                            <p #columnTooltip="matTooltip"
                               [class.ui-grid-header-title-filtered]="isFilterApplied(column)"
                               [matTooltip]="column.title + (focusedColumnHeader ? ('\n' + column.description) : '')"
                               [matTooltipDisabled]="resizeManager.isResizing"
                               [attr.aria-label]="column.title + (column.description ? ('. ' + column.description) : '') + (column.sortable && intl.sortableMessage ? '. ' + intl.sortableMessage : '')"
                               matTooltipClass="preserve-whitespace">
                                {{ column.title }}</p>

                            <mat-icon *ngIf="column.description"
                                      [matTooltip]="column.description"
                                      class="ui-grid-info-icon material-icons-outlined">info</mat-icon>

                            <div *ngIf="column.sortable"
                                 [class.ui-grid-sort-indicator-asc]="column.sort === 'asc'"
                                 [class.ui-grid-sort-indicator-desc]="column.sort === 'desc'"
                                 class="ui-grid-sort-indicator">
                                <mat-icon class="ui-grid-sort-icon">
                                    <svg xmlns="http://www.w3.org/2000/svg"
                                         viewBox="0 0 24 24">
                                        <path [attr.transform]="column.sort === '' ? 'translate(0 -3)': ''"
                                              class="path-asc"
                                              d="M12 11.8l2.5 2.5 1.1-1.1L12 9.7l-3.6 3.5 1.1 1.1z" />
                                        <path [attr.transform]="column.sort === '' ? 'translate(0 3)': ''"
                                              class="path-desc"
                                              d="M12 12.2L9.5 9.7l-1.1 1.1 3.6 3.5 3.6-3.5-1.1-1.1z" />
                                    </svg>
                                </mat-icon>
                            </div>
                        </div>

                        <div *ngIf="column.resizeable && !last && !useCardView"
                             (mousedown)="resizeManager.startResize($event, column, columnIndex)"
                             class="ui-grid-resize-anchor">
                            <mat-icon>
                                <svg xmlns="http://www.w3.org/2000/svg"
                                     width="24"
                                     height="24">
                                    <path
                                          d="M11 18l-2 2-2-2 2-2 2 2zm-2-8l-2 2 2 2 2-2-2-2zm0-6L7 6l2 2 2-2-2-2zm6 4l2-2-2-2-2 2 2 2zm0 2l-2 2 2 2 2-2-2-2zm0 6l-2 2 2 2 2-2-2-2z" />
                                </svg>
                            </mat-icon>
                        </div>
                    </div>

                    <div *ngIf="virtualScroll"
                         [style.marginLeft.px]="scrollCompensationWidth$ | async"
                         class="ui-grid-header-cell ui-grid-scroll-size-compensation-cell ui-grid-feature-cell">
                    </div>
                    <div *ngIf="refreshable"
                         class="ui-grid-header-cell ui-grid-refresh-cell ui-grid-feature-cell"
                         role="columnheader">
                        <button [matTooltip]="intl.refreshTooltip"
                                [matTooltipDisabled]="resizeManager.isResizing"
                                [disabled]="disabled"
                                (click)="refresh.emit()"
                                mat-icon-button
                                type="button"
                                class="grid-refresh-button">
                            <mat-icon>refresh</mat-icon>
                        </button>
                    </div>
                    <div *ngIf="!!actions"
                         class="ui-grid-header-cell ui-grid-action-cell ui-grid-feature-cell">
                    </div>
                </div>
                <mat-progress-bar *ngIf="loading && !loadingState"
                                  mode="query"
                                  class="ui-grid-loader">
                </mat-progress-bar>
            </div>
            <ng-container *ngIf="useLegacyDesign && multiPageSelect then multiPageSelectionRow">
            </ng-container>

            <ng-container *ngIf="dataManager?.length; else noData">
                <ng-container *ngIf="virtualScroll">
                    <cdk-virtual-scroll-viewport [total]="dataManager.length"
                                                 [itemSize]="rowSize"
                                                 uiVirtualScrollViewportResize
                                                 class="ui-grid-viewport">
                        <ng-container *ngIf="useCardView">
                            <div class="ui-grid-cards-container">
                                <ng-container *cdkVirtualFor="let row of dataManager.data$ | async;
                                let last = last;
                                let index = index;
                                trackBy: dataManager.hashTrack">
                                    <ng-container *ngTemplateOutlet="rowCardTemplate; context: {
                                        data: row,
                                        index: index,
                                        expanded: expandedEntries,
                                        last: last
                                    }"></ng-container>
                                </ng-container>
                            </div>
                        </ng-container>
                        <ng-container *ngIf="!useCardView">
                            <ng-container *cdkVirtualFor="let row of dataManager.data$ | async;
                                                      let last = last;
                                                      let index = index;
                                                      trackBy: dataManager.hashTrack">
                                <ng-container  *ngTemplateOutlet="rowTemplate; context: {
                                        data: row,
                                        index: index,
                                        expanded: expandedEntries,
                                        last: last
                                    }">
                                </ng-container>
                            </ng-container>
                        </ng-container>
                    </cdk-virtual-scroll-viewport>
                </ng-container>

                <ng-container *ngIf="!virtualScroll">
                    <ng-container *ngIf="useCardView">
                        <div class="ui-grid-cards-container">
                            <ng-container *ngFor="let row of dataManager.data$ | async;
                                              let index = index;
                                              let last = last;">
                                <ng-container *ngTemplateOutlet="rowCardTemplate; context: {
                                        data: row,
                                        index: index,
                                        expanded: expandedEntries,
                                        last: last
                                    }">
                                </ng-container>
                            </ng-container>
                        </div>
                    </ng-container>
                    <ng-container *ngIf="!useCardView">
                        <ng-container *ngFor="let row of dataManager.data$ | async;
                                          let index = index;
                                          let last = last;">
                            <ng-container  *ngTemplateOutlet="rowTemplate; context: {
                                    data: row,
                                    index: index,
                                    expanded: expandedEntries,
                                    last: last
                                }">
                            </ng-container>
                        </ng-container>
                    </ng-container>
                </ng-container>
            </ng-container>

            <ng-container *ngIf="loading && !!loadingState">
                <ng-container *ngTemplateOutlet="loadingState.html ?? null"></ng-container>
            </ng-container>
        </div>
    </div>
    <div class="ui-grid-footer-container">
        <ng-container *ngTemplateOutlet="useLegacyDesign ? classicFooter  : alternativeFooter"></ng-container>
    </div>
</div>

<ng-template #rowTemplate
             let-row="data"
             let-expanded="expanded"
             let-last="last"
             let-index="index">
    <div cdkMonitorSubtreeFocus
         class="ui-grid-row"
         *ngIf="!isRowExpanded(row?.id) || expandMode === 'preserve'"
         [class.ui-grid-row-state-expanded]="isRowExpanded(row?.id)"
         [class.ui-grid-border-none]="!footer && last"
         [ngClass]="rowConfig?.ngClassFn(row) ?? ''"
         [tabIndex]="0"
         [attr.data-row-index]="index"
         (click)="onRowClick($event, row)"
         (keyup.enter)="onRowClick($event, row)"
         role="row">

        <div *ngIf="isProjected"
             class="ui-grid-cell ui-grid-feature-cell ui-grid-mobile-feature-cell"
             role="gridcell">
            <div *ngIf="selectable"
                 class="ui-grid-mobile-feature-container ui-grid-mobile-refresh-container">
                <mat-checkbox *ngLet="disableSelectionByEntry(row) as disabledReason"
                              (click)="checkShift($event)"
                              (keyup.shift.space)="checkShift($event)"
                              (keyup.space)="checkShift($event)"
                              (change)="handleSelection(index, row)"
                              [checked]="selectionManager.isSelected(row)"
                              [matTooltip]="disabledReason || checkboxTooltip(row)"
                              [aria-label]="disabledReason || checkboxTooltip(row)"
                              [disabled]="!!disabledReason"
                              tabindex="0">
                </mat-checkbox>
            </div>
            <div *ngIf="!!actions"
                 role="gridcell"
                 class="ui-grid-mobile-feature-container ui-grid-mobile-action-container">
                <ng-container *ngTemplateOutlet="actions.html ?? null; context: {
                    data: row,
                    index: index
                }">
                </ng-container>
            </div>
        </div>

        <div *ngIf="!isProjected &&
                    selectable"
             class="ui-grid-cell ui-grid-checkbox-cell ui-grid-feature-cell"
             role="gridcell">
            <mat-checkbox *ngLet="disableSelectionByEntry(row) as disabledReason"
                          (click)="checkShift($event)"
                          (keyup.shift.space)="checkShift($event)"
                          (keyup.space)="checkShift($event)"
                          (change)="handleSelection(index, row)"
                          [checked]="selectionManager.isSelected(row)"
                          [matTooltip]="disabledReason || checkboxTooltip(row)"
                          [aria-label]="disabledReason || checkboxTooltip(row)"
                          [disabled]="disabledReason"
                          tabindex="0">
            </mat-checkbox>
        </div>

        <ng-container *ngFor="let column of renderedColumns$ | async">
            <div [class.ui-grid-primary]="column.directive.primary"
                 [class.ui-grid-secondary]="!column.directive.primary"
                 [class.ui-grid-state-resizing]="column.directive === resizeManager.current?.column"
                 [attr.data-property]="column.directive.property"
                 [attr.data-identifier]="column.directive.identifier"
                 [style.width.%]="column.directive.width"
                 [attr.role]="column.role"
                 class="ui-grid-cell">
                <div *ngIf="isProjected"
                     [matTooltip]="column.directive.title ?? ''"
                     [matTooltipDisabled]="resizeManager.isResizing"
                     class="ui-grid-cell-mobile-title">{{column.directive.title}}</div>
                <div class="ui-grid-cell-content">
                    <ng-container *ngTemplateOutlet="column.directive.html || textCellTemplate; context: {
                            data: row,
                            index: index,
                            property: column.directive.property
                        }">
                    </ng-container>
                </div>
            </div>
        </ng-container>

        <div *ngIf="virtualScroll"
             class="ui-grid-cell ui-grid-scroll-size-compensation-cell ui-grid-feature-cell">
        </div>

        <div *ngIf="!isProjected &&
                    refreshable"
             class="ui-grid-cell ui-grid-refresh-cell ui-grid-feature-cell">
        </div>

        <div *ngIf="!isProjected &&
                    !!actions"
             role="gridcell"
             class="ui-grid-cell ui-grid-action-cell ui-grid-feature-cell">
            <div class="ui-grid-action-cell-container">
                <ng-container *ngTemplateOutlet="actions.html ?? null; context: {
                        data: row,
                        index: index
                    }">
                </ng-container>
            </div>
        </div>
    </div>
    <div *ngIf="isRowExpanded(row?.id)"
         [class.ui-grid-row-state-expanded]="isRowExpanded(row?.id)"
         [class.ui-grid-border-none]="!footer && last"
         role="row">
        <div class="ui-grid-expanded-row-container">
            <ng-container *ngTemplateOutlet="expandedRow!.html ?? null; context: {
                data: row,
                index: index
            }">
            </ng-container>
        </div>
    </div>
</ng-template>

<ng-template #textCellTemplate
             let-row="data"
             let-property="property">
    <p [matTooltip]="dataManager.getProperty(row, property)"
       [matTooltipDisabled]="resizeManager.isResizing">{{ dataManager.getProperty(row, property) }}</p>
</ng-template>

<ng-template #rowCardTemplate
    let-row="data"
    let-expanded="expanded"
    let-last="last"
    let-index="index">
    <div cdkMonitorSubtreeFocus
        class="ui-grid-card-wrapper"
        [ngClass]="rowConfig?.ngClassFn(row) ?? ''"
        [tabIndex]="0"
        [attr.data-row-index]="index"
        (click)="onRowClick($event, row)"
        (keyup.enter)="onRowClick($event, row)"
    >
        <ng-container *ngIf="cardTemplate?.html; else defaultCardTemplate">
            <ng-container *ngTemplateOutlet="cardTemplate?.html || defaultCardTemplate;context: {
                data: row,
                index: index,
                expanded: expandedEntries,
                last: last
            }"></ng-container>
        </ng-container>
    </div>
</ng-template>

<ng-template #defaultCardTemplate
    let-row="data"
    let-index="index">
    <div class="ui-grid-card-default">
        <ng-container *ngFor="let column of renderedColumns$ | async">
            <div [class.ui-grid-primary]="column.directive.primary"
                 [class.ui-grid-secondary]="!column.directive.primary"
                 [attr.data-property]="column.directive.property"
                 [attr.data-identifier]="column.directive.identifier"
                 [attr.role]="column.role"
                 class="ui-grid-card-default-cell">
                <div *ngIf="isProjected"
                     [matTooltip]="column.directive.title ?? ''"
                     [matTooltipDisabled]="resizeManager.isResizing"
                     class="ui-grid-card-default-cell-mobile-title">{{column.directive.title}}</div>
                <div class="ui-grid-card-default-cell-content">
                    <b>{{ column.directive.title ?? column.directive.property }}:</b> <ng-container *ngTemplateOutlet="column.directive.html || textCellTemplate; context: {
                            data: row,
                            index: index,
                            property: column.directive.property
                        }">
                    </ng-container>
                </div>
            </div>
        </ng-container>
    </div>
</ng-template>

<ng-template #noData>
    <ng-container *ngIf="!loading">
        <ng-container *ngIf="noContent; else defaultNoData">
            <ng-container *ngTemplateOutlet="noContent.html ?? null; context: {
                search: header?.searchValue,
                activeCount: filterManager.activeCount$ | async
            }">
            </ng-container>
        </ng-container>
    </ng-container>
</ng-template>

<ng-template #defaultNoData>
    <ng-container *ngIf="useLegacyDesign; else defaultNoDataAlternative">
        <div *ngIf="!dataManager.pristine"
             class="ui-grid-row ui-grid-no-data-container"
             [class.ui-grid-border-none]="!footer">
            <mat-icon>list</mat-icon>
            <p>{{noDataMessage || intl.noDataMessage }}</p>
        </div>
    </ng-container>
</ng-template>

<ng-template #defaultNoDataAlternative>
    <ng-container *ngLet="(filterManager.activeCount$ | async) as activeFilterCount">
        <div *ngIf="!dataManager.pristine"
             class="ui-grid-row ui-grid-no-data-container"
             [class.ui-grid-no-content-available]="!header?.searchValue && !activeFilterCount"
             [class.ui-grid-border-none]="!footer">
            <mat-icon *ngIf="!header?.searchValue && !activeFilterCount">visibility_off</mat-icon>
            <p>{{ noDataMessage || intl.noDataMessageAlternative(header?.searchValue, activeFilterCount) }}</p>
        </div>
    </ng-container>
</ng-template>

<ng-template #multiPageSelectionAlternate>
    <div *ngIf="!isProjected &&
                showMultiPageSelectionInfo"
         class="ui-grid-selection-info-container ui-grid-selection-info-container-alternate">
        <ng-container *ngTemplateOutlet="multiPageSelectionInfo"></ng-container>
    </div>
</ng-template>

<ng-template #multiPageSelectionRow>
    <div *ngIf="!isProjected &&
                showMultiPageSelectionInfo"
         class="ui-grid-row ui-grid-selection-info-container"
         [class.ui-grid-selection-info-container-alternate]="!useLegacyDesign">
        <ng-container *ngTemplateOutlet="multiPageSelectionInfo"></ng-container>
    </div>
</ng-template>

<ng-template #multiPageSelectionProjectedHeaderCell>
    <div *ngIf="isProjected &&
                showMultiPageSelectionInfo"
         class="ui-grid-header-cell ui-grid-feature-cell ui-grid-selection-info-container">
        <ng-container *ngTemplateOutlet="multiPageSelectionInfo"></ng-container>
    </div>
</ng-template>

<ng-template #multiPageSelectionInfo>
    <ng-container *ngIf="selectionManager.selected.length; else noSelectionTmpl">
        <button [matTooltip]="intl.clearSelectionTooltip"
                [matTooltipDisabled]="resizeManager.isResizing"
                (click)="selectionManager.clear()"
                mat-icon-button
                type="button"
                color="warn"
                class="ui-grid-selection-clear-button">
            <mat-icon>clear</mat-icon>
        </button>
        <span class="ui-grid-selection-info-message">
            {{ intl.translateMultiPageSelectionCount(selectionManager.selected.length) }}
        </span>
    </ng-container>
</ng-template>

<ng-template #noSelectionTmpl>
    <mat-icon *ngIf="useLegacyDesign"
              [matTooltip]="intl.multiPageSelectionInfoTooltip"
              [matTooltipDisabled]="resizeManager.isResizing"
              class="ui-grid-selection-info-icon"
              tabindex="0">info</mat-icon>
    <span class="ui-grid-selection-info-message">
        {{ intl.noSelectionMessage }}
    </span>
</ng-template>

<ng-template #classicFooter>
    <mat-paginator *ngIf="!!footer && !footer.hidden"
                   [pageIndex]="footer.state.pageIndex"
                   [pageSize]="footer.state.pageSize"
                   [pageSizeOptions]="footer.pageSizes"
                   [length]="footer.length"
                   [disabled]="disabled"
                   [showFirstLastButtons]="footer.showFirstLastButtons"
                   [hidePageSize]="footer.hidePageSize"
                   (page)="footer.pageChange.next($event)">
    </mat-paginator>
</ng-template>

<ng-template #alternativeFooter>
    <ui-grid-custom-paginator *ngIf="!!footer && !footer.hidden"
                              [pageIndex]="footer.state.pageIndex"
                              [pageSize]="footer.state.pageSize"
                              [pageSizeOptions]="footer.pageSizes"
                              [length]="footer.length"
                              [disabled]="disabled"
                              [showFirstLastButtons]="footer.showFirstLastButtons"
                              [hidePageSize]="footer.hidePageSize"
                              [hideTotalCount]="footer.hideTotalCount"
                              (page)="footer.pageChange.next($event)">
    </ui-grid-custom-paginator>
</ng-template>

<ng-template #filtersTmpl>
    <ng-container *ngIf="areFilersCollapsed$ | async; else inlineFiltersTmpl">
        <button *ngIf="hasAnyFiltersVisible$ | async"
                [disabled]="disabled"
                (click)="showFilters = !showFilters"
                [attr.aria-expanded]="showFilters"
                mat-button
                type="button"
                class="ui-grid-collapsible-filters-toggle">
            <mat-icon>filter_list</mat-icon>
            <span>{{ intl.filters(filterManager.activeCount$ | async) }}</span>
            <mat-icon>{{ showFilters ? 'expand_less' : 'expand_more' }}</mat-icon>
        </button>
    </ng-container>
</ng-template>

<ng-template #inlineFiltersTmpl>
    <ng-container *ngFor="let column of columns$ | async">
        <div *ngIf="column.dropdown?.visible$ | async"
             [ngClass]="{'ui-grid-alternate-filter-container': collapsibleFilters}"
             class="ui-grid-dropdown-filter-container ui-grid-filter-option">
            <button [matMenuTriggerFor]="filterOptions"
                    [disabled]="column.dropdown?.disabled || disabled"
                    [attr.data-column-name]="getColumnName(column)"
                    [expandedTranslation]="intl.menuExpanded"
                    uiCustomMatMenuTriggerFor
                    mat-button
                    type="button"
                    class="ui-grid-dropdown-filter-button">
                <span class="ui-grid-dropdown-filter-title">{{ column.title }}: </span>
                <span class="ui-grid-dropdown-filter-value">
                    {{ !!column.dropdown?.value ? intl.translateDropdownOption(column.dropdown!.value!) :
                    intl.noFilterPlaceholder }}
                </span>
                <mat-icon>keyboard_arrow_down</mat-icon>
            </button>
            <mat-menu #filterOptions="matMenu"
                      [overlapTrigger]="true">
                <button *ngIf="column.dropdown?.showAllOption"
                        [class.active]="column.dropdown?.value == null"
                        [matTooltip]="intl.noFilterPlaceholder"
                        (click)="filterManager.dropdownUpdate(column!, undefined)"
                        type="button"
                        mat-menu-item>
                    {{ intl.noFilterPlaceholder }}
                </button>
                <button *ngFor="let dropdownItem of column.dropdown?.items"
                        [class.active]="column.dropdown?.value?.label === dropdownItem.label"
                        [matTooltip]="intl.translateDropdownOption(dropdownItem)"
                        (click)="filterManager.dropdownUpdate(column, dropdownItem)"
                        type="button"
                        mat-menu-item>
                    {{ intl.translateDropdownOption(dropdownItem) }}
                </button>
            </mat-menu>
        </div>

        <ng-container *ngIf="column.searchableDropdown?.visible$ | async">
            <ui-suggest #suggest
                        [placeholder]="column.title ?? ''"
                        [defaultValue]="column.searchableDropdown!.noFilterPlaceholder || intl.noFilterPlaceholder"
                        [searchSourceFactory]="column.searchableDropdown!.searchSourceFactory"
                        [value]="searchableDropdownValue(column.searchableDropdown!)"
                        [disabled]="column.searchableDropdown!.disabled || disabled"
                        [drillDown]="column.searchableDropdown!.drillDown"
                        [maxLength]="64"
                        [fetchStrategy]="column.searchableDropdown!.fetchStrategy || fetchStrategy"
                        [attr.data-cy]="getColumnName(column, 'ui-grid-search-filter')"
                        [multiple]="column.searchableDropdown!.multiple"
                        [compact]="column.searchableDropdown!.multiple"
                        [displayCount]="column.searchableDropdown!.displayCount"
                        [minChars]="column.searchableDropdown!.minChars ?? 0"
                        (selected)="filterManager.searchableDropdownUpdate(column, $event, true)"
                        (deselected)="filterManager.searchableDropdownUpdate(column, $event, false)"
                        (opened)="column.refetch && suggest.fetch()"
                        width="230px"
                        class="ui-grid-search-filter ui-grid-filter-option">
            </ui-suggest>
        </ng-container>
    </ng-container>
</ng-template>

<ng-template #toggleColumnsTmpl>
    <ui-grid-toggle-columns *ngIf="toggleColumns && !(hasSelection$ | async)"
                            [options]="visibilityManager.options$ | async"
                            [dirty]="(visibilityManager.isDirty$ | async) ?? false"
                            [toggleTooltip]="intl.toggleTooltip"
                            [resetToDefaults]="intl.toggleResetToDefaults"
                            [toggleTitle]="intl.toggleTitle"
                            [togglePlaceholderTitle]="intl.togglePlaceholderTitle"
                            [useLegacyDesign]="useLegacyDesign"
                            [showDivider]="!useLegacyDesign && (displayToggleColumnsDivider$ | async) ?? false"
                            (visibleColumns)="visibilityManager.update($event)"
                            (resetColumns)="visibilityManager.reset()"
                            (visibleColumnsToggled)="visibleColumnsToggle$.next($event)">
    </ui-grid-toggle-columns>
</ng-template>

./ui-grid.component.scss

@import "../../styles/ellipse";

.preserve-whitespace {
    word-wrap: break-word;
    white-space: pre-line;
    text-align: left;
}

ui-grid {
    $ui-grid-header-row-height: 56px;
    $ui-grid-header-alternate-row-height: 52px;
    $ui-grid-row-height: 48px;
    $ui-grid-row-horizontal-padding: 24px;
    $ui-feature-cell-width: 50px;
    $ui-row-border-width: 1px;
    $ui-header-resize-handle-width: 15px;
    $ui-grid-default-spacing: 5px;
    $ui-border-radius: 4px;
    $ui-grid-button-size: 40px;
    $ui-grid-main-font-size: 13.5px;
    $ui-grid-secondary-font-size: 12px;
    $ui-grid-action-buttons-spacing: 16px;

    position: relative;
    display: block;

    &.ui-grid-mode-multi-select {
        .ui-grid-table {
            cdk-virtual-scroll-viewport.ui-grid-viewport {
                min-height: $ui-grid-row-height * 3;
                height: calc(100vh - 300px - #{$ui-grid-row-height});

                .cdk-virtual-scroll-content-wrapper {
                    width: 100%;
                }
            }
        }
    }

    .ui-grid-table {
        cdk-virtual-scroll-viewport.ui-grid-viewport {
            min-height: $ui-grid-row-height * 3;
            height: calc(100vh - 300px);

            .cdk-virtual-scroll-content-wrapper {
                width: 100%;
            }
        }
    }

    .ui-grid-debug-information {
        position: absolute;
        text-align: right;
        bottom: -30px;
        width: 400px;
        right: 0;
    }

    &.ui-grid-state-projected {
        .ui-grid-action-button {
            right: 80px;
        }

        .ui-grid {
            &-table {
                $ui-grid-projected-spacing-lr: 24px;
                $ui-grid-projected-spacing-tb: 8px;

                .ui-grid-cell-mobile-title {
                    width: 115px;
                    display: inline-block;
                    font-weight: bold;
                    text-align: left;
                }

                .ui-grid-header-cell {
                    &:not(.ui-grid-refresh-cell):not(.ui-grid-checkbox-cell):not(.ui-grid-selection-info-container) {
                        display: none;
                    }

                    &.ui-grid-refresh-cell {
                        justify-content: flex-end;
                        margin-right: $ui-grid-projected-spacing-lr;
                    }

                    &.ui-grid-checkbox-cell {
                        overflow: visible;
                        justify-content: flex-start;
                        margin-left: $ui-grid-projected-spacing-lr + $ui-grid-default-spacing;
                    }
                }

                .ui-grid-row {
                    height: auto;
                    flex-direction: column;
                    align-items: start;
                    padding: $ui-grid-projected-spacing-tb $ui-grid-projected-spacing-lr;
                }

                .ui-grid-cell.ui-grid-mobile-feature-cell {
                    margin-left: $ui-grid-default-spacing;
                    overflow: visible;
                    justify-content: flex-start;

                    .ui-grid-mobile-feature-container {
                        margin: 0 $ui-grid-default-spacing;

                        &:first-of-type {
                            margin-left: 0;
                        }
                    }
                }

                .ui-grid-cell {
                    justify-content: space-between;
                    padding: 0;
                    width: 100% !important;
                }
            }
        }

        .mat-paginator-container {
            justify-content: center;
        }
    }
    .mat-paginator-container {
        padding: 0 $ui-grid-default-spacing;
    }

    &.ui-grid-state-resizing {
        .ui-grid-header-row {
            user-select: none;
        }
    }

    .ui-grid {
        &-primary,
        &-header-cell {
            font-weight: 500;
        }

        &-filter-container {
            padding: 0 0 $ui-grid-default-spacing $ui-grid-default-spacing;
            min-height: $ui-grid-header-row-height;

            display: flex;
            flex-wrap: wrap;
            align-items: flex-start;

            &-lhs-group {
                display: flex;
                flex-direction: column;

                &-actions {
                    min-height: $ui-grid-header-row-height;
                    display: flex;
                    flex-wrap: wrap;
                    align-items: center;
                }

                &-filters {
                    min-height: $ui-grid-header-row-height;
                    flex-wrap: wrap;
                    align-items: center;
                    display: flex;
                }
            }

            &-rhs-group {
                min-height: $ui-grid-header-row-height;
                display: flex;
                margin-left: auto;
                align-items: center;

                .ui-grid-action-buttons-main {
                    display: flex;
                    gap: $ui-grid-action-buttons-spacing;
                }
            }
        }

        // single main button on default design
        &-action-button {
            position: absolute;
            right: $ui-grid-button-size;
            top: -21px;
            z-index: 2;
        }

        &-action-buttons {
            &-main {
                margin-left: auto;
            }
        }

        &-container {
            border-radius: $ui-border-radius;

            .mat-paginator {
                &-container {
                    min-height: $ui-grid-header-row-height - 1;
                }

                .mat-paginator-page-size-label,
                .mat-paginator-range-label,
                .mat-select-value {
                    font-size: $ui-grid-secondary-font-size;
                    line-height: $ui-grid-secondary-font-size;
                }

                .mat-form-field-infix {
                    padding: 0;

                    .mat-select {
                        padding: 0.4375em 0;
                    }
                }
            }

            &.use-alternate-design {
                .ui-grid-header {
                    &-row,
                    &-cell {
                        height: $ui-grid-header-alternate-row-height;
                    }
                }

                .ui-grid-selection-info {
                    &-clear-button {
                        order: 1;
                    }
                }

                .ui-grid-no-data-container {
                    height: 4 * $ui-grid-row-height;
                    align-items: flex-start;

                    &.ui-grid-no-content-available {
                        align-items: center;
                        justify-content: center;
                        flex-direction: column;
                    }
                }

                ui-grid-custom-paginator {
                    .mat-paginator-page-label {
                        font-size: $ui-grid-secondary-font-size;
                    }
                    .mat-paginator-page-size .mat-form-field {
                        padding: unset;
                        margin: unset;

                        .mat-select {
                            padding: 0.4375em 0;
                        }
                    }
                }
            }
        }

        &-table-container {
            position: relative;
        }

        &-no-data-container {
            padding: $ui-grid-default-spacing;

            mat-icon {
                font-size: 32px;
                width: 32px;
                height: 32px;
                margin-left: $ui-grid-default-spacing;
            }

            p {
                margin-left: $ui-grid-default-spacing;
            }
        }

        &-selection-info-container {
            &-alternate {
                min-height: $ui-grid-button-size;
                padding: $ui-grid-default-spacing 0;
                display: flex;
                align-items: center;

                .ui-grid-selection {
                    &-clear-button {
                        order: 1;
                    }
                }
            }

            button.mat-icon-button,
            .ui-grid-selection-info-icon {
                margin-left: 5px;
            }
            .ui-grid-selection-info-message {
                margin-left: $ui-grid-default-spacing * 2;
            }

            .ui-grid-selection-info-icon {
                // simulate same size as button
                outline: none;
                padding: 8px;
            }

            &.ui-grid-header-cell {
                width: 100%;
                display: flex;
                justify-content: flex-start;
                flex-direction: row-reverse;
                margin-right: $ui-grid-button-size + $ui-grid-row-horizontal-padding;
            }
        }

        &-resize-anchor {
            height: 100%;
            width: $ui-header-resize-handle-width + $ui-grid-default-spacing;
            cursor: col-resize;
            justify-content: center;
            display: flex;
            align-items: center;

            mat-icon {
                margin-right: $ui-grid-default-spacing;
            }
        }

        &-dropdown-filter {
            &-button {
                text-transform: none !important;
                font-size: 0.8rem;
                height: $ui-grid-button-size;
                line-height: normal;
                padding-right: 6px;

                .ui-grid-dropdown-filter-title {
                    font-weight: 700;
                }
            }
        }

        &-filter-option {
            margin-right: $ui-grid-default-spacing * 2;

            &.ui-grid-search {
                $search-padding: $ui-grid-default-spacing;
                bottom: $search-padding;
                position: relative;
                height: $ui-grid-header-row-height - $search-padding - 1px;
            }

            &:last-child {
                margin-right: 0;
            }
        }

        &-table {
            display: block;

            .ui-grid-header-title {
                width: calc(100% - #{$ui-header-resize-handle-width + $ui-grid-default-spacing});
                height: 100%;
                display: inline-flex;
                align-items: center;

                p {
                    @extend %ellipse;
                }
            }

            .ui-grid-cell-content {
                @extend %ellipse;

                > * {
                    @extend %ellipse;
                }
            }

            .ui-grid-header-row {
                font-size: $ui-grid-secondary-font-size;
                line-height: $ui-grid-secondary-font-size;
                text-transform: uppercase;
                height: $ui-grid-header-row-height;
                position: relative;
            }

            .ui-grid-header {
                mat-progress-bar {
                    position: absolute;
                    right: 0;
                }
            }

            .ui-grid-expanded-row-container {
                width: 100%;
                height: 100%;
                padding: $ui-grid-default-spacing;
                box-sizing: border-box;
            }

            .ui-grid-row {
                height: $ui-grid-row-height;
                font-size: $ui-grid-main-font-size;

                &.ui-grid-row-state-expanded {
                    height: auto;
                }
            }

            .ui-grid-header-row,
            .ui-grid-row,
            .ui-grid-row-state-expanded {
                display: flex;
                align-items: center;
                border-width: 0;
                box-sizing: border-box;

                &:not(.ui-grid-border-none) {
                    border-style: solid;
                    border-bottom-width: 1px;
                }
            }

            &:not(.ui-grid-table-refreshable) {
                .ui-grid-action-cell {
                    min-width: $ui-feature-cell-width;
                }
            }

            .ui-grid-cell,
            .ui-grid-header-cell {
                flex: 1;
                flex-basis: auto;
                display: flex;
                align-items: center;
                overflow: hidden;
                word-wrap: break-word;

                &:not(.ui-grid-feature-cell):not(:first-child) {
                    box-sizing: border-box;
                    padding: 0 0 0 $ui-grid-default-spacing;
                }

                &:not(.ui-grid-feature-cell):first-of-type {
                    padding-left: $ui-grid-row-horizontal-padding;

                    [dir="rtl"] & {
                        padding-left: 0;
                        padding-right: $ui-grid-row-horizontal-padding;
                    }
                }

                &:not(.ui-grid-feature-cell):last-of-type {
                    padding-right: $ui-grid-row-horizontal-padding;

                    [dir="rtl"] & {
                        padding-right: 0;
                        padding-left: $ui-grid-row-horizontal-padding;
                    }
                }

                &.ui-grid-refresh-cell,
                &.ui-grid-checkbox-cell {
                    min-width: $ui-feature-cell-width;
                    justify-content: center;
                }

                &.ui-grid-action-cell {
                    position: relative;
                    width: 0;
                    padding: 0;
                    overflow: visible;

                    > div {
                        display: inline-flex;
                        justify-content: flex-end;
                        align-items: center;
                        position: absolute;
                        height: $ui-grid-row-height - $ui-row-border-width;
                        min-width: $ui-feature-cell-width;
                        padding-right: $ui-grid-default-spacing;
                        right: 0;
                        bottom: 0;
                    }
                }
            }

            .ui-grid-sort {
                &-indicator {
                    height: $ui-grid-header-row-height - $ui-row-border-width;
                    align-items: center;
                    display: flex;

                    //sorted asc
                    &-asc {
                        // desc path
                        .path-desc {
                            opacity: 0;
                        }
                    }
                    &-desc {
                        //sorted desc
                        .path-asc {
                            // asc path
                            opacity: 0;
                        }
                    }
                }
            }

            .ui-grid-header-cell {
                height: $ui-grid-header-row-height - $ui-row-border-width;
            }

            .ui-grid-cell {
                height: $ui-grid-row-height - $ui-row-border-width;
            }

            .ui-grid-header-cell-sortable {
                cursor: pointer;
            }

            .ui-grid-cards-container {
                margin: 16px;
                display: flex;
                flex-direction: row;
                flex-wrap: wrap;

                @supports (display: grid) {
                    display: grid;
                    grid-column-gap: 12px;
                    grid-row-gap: 16px;
                    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
                }
            }

            .ui-grid-card-default {
                border-radius: 5px;
                background: #ffffff;
                border: 1px solid #cfd8dd;
                color: #273139;
                padding: 16px;
            }

            .ui-grid-card-default-cell-content {
                display: flex;
                align-items: center;
                gap: 8px;
            }
        }
    }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""