forked from angular/components
/
sort.ts
283 lines (241 loc) · 8.18 KB
/
sort.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/**
* @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.io/license
*/
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {
Directive,
EventEmitter,
Inject,
InjectionToken,
Input,
OnChanges,
OnDestroy,
OnInit,
Optional,
Output,
SimpleChanges,
} from '@angular/core';
import {CanDisable, HasInitialized, mixinDisabled, mixinInitialized} from '@angular/material/core';
import {Subject} from 'rxjs';
import {SortDirection} from './sort-direction';
import {
getSortDuplicateSortableIdError,
getSortHeaderMissingIdError,
getSortInvalidDirectionError,
} from './sort-errors';
/** 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: 'asc' | 'desc';
/** 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;
}
/** Injection token to be used to override the default options for `mat-sort`. */
export const MAT_SORT_DEFAULT_OPTIONS = new InjectionToken<MatSortDefaultOptions>(
'MAT_SORT_DEFAULT_OPTIONS',
);
// Boilerplate for applying mixins to MatSort.
/** @docs-private */
const _MatSortBase = mixinInitialized(mixinDisabled(class {}));
/** Container for MatSortables to manage the sort state and provide default sort parameters. */
@Directive({
selector: '[matSort]',
exportAs: 'matSort',
host: {'class': 'mat-sort'},
inputs: ['disabled: matSortDisabled'],
})
export class MatSort
extends _MatSortBase
implements CanDisable, HasInitialized, OnChanges, OnDestroy, OnInit
{
/** Collection of all registered sortables that this directive manages. */
sortables = new Map<string, MatSortable>();
// Holds the sort state of any MatSortable registered within this directive
sortState = new Map<string, Sort>();
/** Used to notify any child components listening to state changes. */
readonly _stateChanges = new Subject<void>();
/** The id of the most recently sorted MatSortable. */
@Input('matSortActive') active: string;
/**
* The direction to set when an MatSortable is initially sorted.
* May be overriden by the MatSortable's sort start.
*/
@Input('matSortStart') start: 'asc' | 'desc' = 'asc';
/**
* If MatSort should enable multiple active MatSortables or not.
* Defaults to false.
*/
@Input('matSortMultiple')
get multiple(): boolean {
return this._multiple;
}
set multiple(v: any) {
this._multiple = coerceBooleanProperty(v);
}
private _multiple = false;
/** The sort direction of the most recently sorted 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 overriden by the MatSortable's disable clear input.
*/
@Input('matSortDisableClear')
get disableClear(): boolean {
return this._disableClear;
}
set disableClear(v: boolean) {
this._disableClear = coerceBooleanProperty(v);
}
private _disableClear: boolean;
/** Event emitted when the user changes either the active sort or sort direction. */
@Output('matSortChange') readonly sortChange: EventEmitter<Sort> = new EventEmitter<Sort>();
constructor(
@Optional()
@Inject(MAT_SORT_DEFAULT_OPTIONS)
private _defaultOptions?: MatSortDefaultOptions,
) {
super();
}
/**
* 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 {
let sortableDirection;
if (!this.isActive(sortable.id)) {
sortableDirection = sortable.start ? sortable.start : this.start;
} else {
sortableDirection = this.getNextSortDirection(sortable);
}
const currentSort: Sort = {
active: sortable.id,
direction: sortableDirection,
};
// We should keep only one entry on sortState when single sorting.
if (!this.multiple) {
this.sortState.clear();
}
this.sortState.set(sortable.id, currentSort);
this.sortChange.emit(currentSort);
}
/**
* Checks wether the provided MatSortable is currently active (has sorting) on this MatSort.
*/
isActive(id: string): boolean {
return this.sortState.has(id);
}
/**
* Returns the current sort direction of the supplied sortable,
* default to unsorted if provided an inactive MatSortable ]
*/
getCurrentSortDirection(id: string): SortDirection {
return this.sortState.get(id)?.direction || '';
}
/** Returns the next sort direction of the active sortable, checking for potential overrides. */
getNextSortDirection(sortable: MatSortable): SortDirection {
if (!sortable) {
return '';
}
const currentSortableDirection = this.getCurrentSortDirection(sortable.id);
// 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(currentSortableDirection) + 1;
if (nextDirectionIndex >= sortDirectionCycle.length) {
nextDirectionIndex = 0;
}
return sortDirectionCycle[nextDirectionIndex];
}
ngOnInit() {
this._markInitialized();
}
ngOnChanges(changes: SimpleChanges) {
// We should update sortState with the current active and direction @Input values
// whenever they change to allow programatically sorting by this column.
if (changes['active'] || changes['direction']) {
const activeValue = changes['active']?.currentValue || this.active;
const activeDirection = changes['direction']?.currentValue || this.direction || this.start;
// Handle possibly deactivating sort on active.
if (!activeValue || activeValue === '') {
this.sortState.delete(activeValue);
} else {
const sort: Sort = {
active: activeValue,
direction: activeDirection || this.start,
};
this.sortState.set(sort.active, sort);
}
}
this._stateChanges.next();
}
ngOnDestroy() {
this._stateChanges.complete();
}
static ngAcceptInputType_disableClear: BooleanInput;
static ngAcceptInputType_disabled: BooleanInput;
}
/** Returns the sort direction cycle to use given the provided parameters of order and clear. */
function getSortDirectionCycle(start: 'asc' | 'desc', disableClear: boolean): SortDirection[] {
let sortOrder: SortDirection[] = ['asc', 'desc'];
if (start == 'desc') {
sortOrder.reverse();
}
if (!disableClear) {
sortOrder.push('');
}
return sortOrder;
}