forked from angular/components
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sort.ts
291 lines (246 loc) · 8.44 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
284
285
286
287
288
289
290
291
/**
* @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 {
booleanAttribute,
Directive,
EventEmitter,
Inject,
InjectionToken,
Input,
OnChanges,
OnDestroy,
OnInit,
Optional,
Output,
SimpleChanges,
} from '@angular/core';
import {HasInitialized, mixinInitialized} from '@angular/material/core';
import {Subject} from 'rxjs';
import {SortDirection} from './sort-direction';
import {
getSortDuplicateSortableIdError,
getSortHeaderMissingIdError,
getSortInvalidDirectionError,
} from './sort-errors';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
/** 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<MatSortDefaultOptions>(
'MAT_SORT_DEFAULT_OPTIONS',
);
// Boilerplate for applying mixins to MatSort.
/** @docs-private */
const _MatSortBase = mixinInitialized(class {});
/** Container for MatSortables to manage the sort state and provide default sort parameters. */
@Directive({
selector: '[matSort]',
exportAs: 'matSort',
host: {
'class': 'mat-sort',
},
standalone: true,
})
export class MatSort extends _MatSortBase implements HasInitialized, OnChanges, OnDestroy, OnInit {
/** Collection of all registered sortables that this directive manages. */
sortables = new Map<string, MatSortable>();
/** Map holding the sort state for each column */
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 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 enable the multi-sorting feature */
@Input('matSortMultiple')
get matSortMultiple(): boolean {
return this._sortMultiple;
}
set matSortMultiple(value: any) {
this._sortMultiple = coerceBooleanProperty(value);
}
private _sortMultiple = false;
/**
* 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<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 ?? this.start;
} else {
sortableDirection = this.getNextSortDirection(sortable);
}
// avoid keeping multiple sorts if not required.
if (!this._sortMultiple) {
this.sortState.clear();
}
// Update active and direction to keep backwards compatibility
if (this.active != sortable.id) {
this.active = sortable.id;
}
this.direction = sortableDirection;
const currentSort: Sort = {
active: sortable.id,
direction: sortableDirection,
};
// When unsorted, remove from state
if (sortableDirection !== '') {
this.sortState.set(sortable.id, currentSort);
} else {
this.sortState.delete(sortable.id);
}
this.sortChange.emit(currentSort);
}
/**
* Checks whether the provided column is currently active (has been sorted)
*/
isActive(id: string): boolean {
return this.sortState.has(id);
}
/**
* Returns the current SortDirection of the supplied column id, defaults to unsorted if no state is found.
*/
getCurrentSortDirection(id: string): SortDirection {
return this.sortState.get(id)?.direction
?? this.sortables.get(id)?.start
?? this.start;
}
/** 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) {
/* Update sortState with updated active and direction values, otherwise sorting won't work */
if (changes['active'] || changes['direction']) {
const currentActive = changes['active']?.currentValue ?? this.active;
const currentDirection = changes['direction']?.currentValue ?? this.direction ?? this.start;
// Handle sort deactivation
if ((!currentActive || currentActive === '') && changes['active']?.previousValue) {
this.sortState.delete(changes['active'].previousValue);
} else {
this.sortState.set(currentActive, {
active: currentActive,
direction: currentDirection,
} as Sort);
}
}
this._stateChanges.next();
}
ngOnDestroy() {
this._stateChanges.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;
}