/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import { Directive, EventEmitter, Inject, InjectionToken, Input, OnChanges, OnDestroy, OnInit, Optional, Output, booleanAttribute, } from '@angular/core'; import {Observable, ReplaySubject, Subject} from 'rxjs'; import {SortDirection} from './sort-direction'; import { getSortDuplicateSortableIdError, getSortHeaderMissingIdError, getSortInvalidDirectionError, } from './sort-errors'; /** Position of the arrow that displays when sorted. */ export type SortHeaderArrowPosition = 'before' | 'after'; /** Interface for a directive that holds sorting state consumed by `MatSortHeader`. */ export interface MatSortable { /** The id of the column being sorted. */ id: string; /** Starting sort direction. */ start: SortDirection; /** Whether to disable clearing the sorting state. */ disableClear: boolean; } /** The current sort state. */ export interface Sort { /** The id of the column being sorted. */ active: string; /** The sort direction. */ direction: SortDirection; } /** Default options for `mat-sort`. */ export interface MatSortDefaultOptions { /** Whether to disable clearing the sorting state. */ disableClear?: boolean; /** Position of the arrow that displays when sorted. */ arrowPosition?: SortHeaderArrowPosition; } /** Injection token to be used to override the default options for `mat-sort`. */ export const MAT_SORT_DEFAULT_OPTIONS = new InjectionToken( 'MAT_SORT_DEFAULT_OPTIONS', ); /** Container for MatSortables to manage the sort state and provide default sort parameters. */ @Directive({ selector: '[matSort]', exportAs: 'matSort', host: { 'class': 'mat-sort', }, }) export class MatSort implements OnChanges, OnDestroy, OnInit { private _initializedStream = new ReplaySubject(1); /** Collection of all registered sortables that this directive manages. */ sortables = new Map(); /** Used to notify any child components listening to state changes. */ readonly _stateChanges = new Subject(); /** The id of the most recently sorted MatSortable. */ @Input('matSortActive') active: string; /** * The direction to set when an MatSortable is initially sorted. * May be overridden by the MatSortable's sort start. */ @Input('matSortStart') start: SortDirection = 'asc'; /** The sort direction of the currently active MatSortable. */ @Input('matSortDirection') get direction(): SortDirection { return this._direction; } set direction(direction: SortDirection) { if ( direction && direction !== 'asc' && direction !== 'desc' && (typeof ngDevMode === 'undefined' || ngDevMode) ) { throw getSortInvalidDirectionError(direction); } this._direction = direction; } private _direction: SortDirection = ''; /** * Whether to disable the user from clearing the sort by finishing the sort direction cycle. * May be overridden by the MatSortable's disable clear input. */ @Input({alias: 'matSortDisableClear', transform: booleanAttribute}) disableClear: boolean; /** Whether the sortable is disabled. */ @Input({alias: 'matSortDisabled', transform: booleanAttribute}) disabled: boolean = false; /** Event emitted when the user changes either the active sort or sort direction. */ @Output('matSortChange') readonly sortChange: EventEmitter = new EventEmitter(); /** Emits when the paginator is initialized. */ initialized: Observable = this._initializedStream; constructor( @Optional() @Inject(MAT_SORT_DEFAULT_OPTIONS) private _defaultOptions?: MatSortDefaultOptions, ) {} /** * Register function to be used by the contained MatSortables. Adds the MatSortable to the * collection of MatSortables. */ register(sortable: MatSortable): void { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!sortable.id) { throw getSortHeaderMissingIdError(); } if (this.sortables.has(sortable.id)) { throw getSortDuplicateSortableIdError(sortable.id); } } this.sortables.set(sortable.id, sortable); } /** * Unregister function to be used by the contained MatSortables. Removes the MatSortable from the * collection of contained MatSortables. */ deregister(sortable: MatSortable): void { this.sortables.delete(sortable.id); } /** Sets the active sort id and determines the new sort direction. */ sort(sortable: MatSortable): void { if (this.active != sortable.id) { this.active = sortable.id; this.direction = sortable.start ? sortable.start : this.start; } else { this.direction = this.getNextSortDirection(sortable); } this.sortChange.emit({active: this.active, direction: this.direction}); } /** Returns the next sort direction of the active sortable, checking for potential overrides. */ getNextSortDirection(sortable: MatSortable): SortDirection { if (!sortable) { return ''; } // Get the sort direction cycle with the potential sortable overrides. const disableClear = sortable?.disableClear ?? this.disableClear ?? !!this._defaultOptions?.disableClear; let sortDirectionCycle = getSortDirectionCycle(sortable.start || this.start, disableClear); // Get and return the next direction in the cycle let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1; if (nextDirectionIndex >= sortDirectionCycle.length) { nextDirectionIndex = 0; } return sortDirectionCycle[nextDirectionIndex]; } ngOnInit() { this._initializedStream.next(); } ngOnChanges() { this._stateChanges.next(); } ngOnDestroy() { this._stateChanges.complete(); this._initializedStream.complete(); } } /** Returns the sort direction cycle to use given the provided parameters of order and clear. */ function getSortDirectionCycle(start: SortDirection, disableClear: boolean): SortDirection[] { let sortOrder: SortDirection[] = ['asc', 'desc']; if (start == 'desc') { sortOrder.reverse(); } if (!disableClear) { sortOrder.push(''); } return sortOrder; }