diff --git a/goldens/material/sort/index.api.md b/goldens/material/sort/index.api.md index 6abd820959cc..aee073ebacd6 100644 --- a/goldens/material/sort/index.api.md +++ b/goldens/material/sort/index.api.md @@ -14,6 +14,7 @@ import { OnChanges } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; import { Subject } from 'rxjs'; +import { TemplateRef } from '@angular/core'; // @public @deprecated export type ArrowViewState = SortDirection | 'hint' | 'active'; @@ -73,6 +74,7 @@ export interface MatSortable { export interface MatSortDefaultOptions { arrowPosition?: SortHeaderArrowPosition; disableClear?: boolean; + icons?: SortHeaderIcons; } // @public @@ -83,6 +85,7 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI arrowPosition: SortHeaderArrowPosition; // (undocumented) _columnDef: MatSortHeaderColumnDef | null; + get defaultIcons(): SortHeaderIcons | undefined; disableClear: boolean; disabled: boolean; _getAriaSortAttribute(): "none" | "ascending" | "descending"; @@ -110,10 +113,11 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI _sort: MatSort; get sortActionDescription(): string; set sortActionDescription(value: string); + readonly sortIconsTemplate: i0.InputSignal | null>; start: SortDirection; _toggleOnInteraction(): void; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -149,6 +153,13 @@ export type SortDirection = 'asc' | 'desc' | ''; // @public export type SortHeaderArrowPosition = 'before' | 'after'; +// @public +export interface SortHeaderIcons { + ascending: string; + default?: string; + descending: string; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/src/components-examples/material/sort/BUILD.bazel b/src/components-examples/material/sort/BUILD.bazel index ccf43f58ffca..7c924d672622 100644 --- a/src/components-examples/material/sort/BUILD.bazel +++ b/src/components-examples/material/sort/BUILD.bazel @@ -18,6 +18,7 @@ ng_project( "//:node_modules/@types/jasmine", "//src/cdk/testing", "//src/cdk/testing/testbed", + "//src/material/icon", "//src/material/sort", "//src/material/sort/testing", ], diff --git a/src/components-examples/material/sort/index.ts b/src/components-examples/material/sort/index.ts index 9b231167c26a..2a3a4066533d 100644 --- a/src/components-examples/material/sort/index.ts +++ b/src/components-examples/material/sort/index.ts @@ -1,2 +1,6 @@ export {SortOverviewExample} from './sort-overview/sort-overview-example'; +export {SortHeaderIconsExample} from './sort-header-icons/sort-header-icons-example'; +export {SortHeaderIconsWithDefaultExample} from './sort-header-icons-with-default/sort-header-icons-with-default-example'; +export {SortHeaderCustomExample} from './sort-header-custom/sort-header-custom-example'; +export {SortHeaderCustomWithDefaultExample} from './sort-header-custom-with-default/sort-header-custom-with-default-example'; export {SortHarnessExample} from './sort-harness/sort-harness-example'; diff --git a/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.css b/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.css new file mode 100644 index 000000000000..5d2cd6d6ab47 --- /dev/null +++ b/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.css @@ -0,0 +1,3 @@ +.mat-sort-header-container { + align-items: center; +} diff --git a/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.html b/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.html new file mode 100644 index 000000000000..ae3c6c55e209 --- /dev/null +++ b/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.html @@ -0,0 +1,33 @@ + + @if (!sortHeader.isDisabled && sortHeader.isSorted) { + @if (sortHeader.direction === 'desc') { + + } + @else { + + } + } + @else { + + } + + + + + + + + + + + + @for (dessert of sortedData; track dessert) { + + + + + + + + } +
Dessert (100g)CaloriesFat (g)Carbs (g)Protein (g)
{{dessert.name}}{{dessert.calories}}{{dessert.fat}}{{dessert.carbs}}{{dessert.protein}}
diff --git a/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.ts b/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.ts new file mode 100644 index 000000000000..365d71d72cee --- /dev/null +++ b/src/components-examples/material/sort/sort-header-custom-with-default/sort-header-custom-with-default-example.ts @@ -0,0 +1,66 @@ +import {Component} from '@angular/core'; +import {MatIcon} from '@angular/material/icon'; +import {Sort, MatSortModule} from '@angular/material/sort'; + +export interface Dessert { + calories: number; + carbs: number; + fat: number; + name: string; + protein: number; +} + +/** + * @title Sorting header with custom template (including default). + */ +@Component({ + selector: 'sort-header-custom-with-default-example', + templateUrl: 'sort-header-custom-with-default-example.html', + styleUrl: 'sort-header-custom-with-default-example.css', + imports: [MatSortModule, MatIcon], +}) +export class SortHeaderCustomWithDefaultExample { + desserts: Dessert[] = [ + {name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4}, + {name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4}, + {name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6}, + {name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4}, + {name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4}, + ]; + + sortedData: Dessert[]; + + constructor() { + this.sortedData = this.desserts.slice(); + } + + sortData(sort: Sort) { + const data = this.desserts.slice(); + if (!sort.active || sort.direction === '') { + this.sortedData = data; + return; + } + + this.sortedData = data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + switch (sort.active) { + case 'name': + return compare(a.name, b.name, isAsc); + case 'calories': + return compare(a.calories, b.calories, isAsc); + case 'fat': + return compare(a.fat, b.fat, isAsc); + case 'carbs': + return compare(a.carbs, b.carbs, isAsc); + case 'protein': + return compare(a.protein, b.protein, isAsc); + default: + return 0; + } + }); + } +} + +function compare(a: number | string, b: number | string, isAsc: boolean) { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); +} diff --git a/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.css b/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.css new file mode 100644 index 000000000000..686a4b3a1c4d --- /dev/null +++ b/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.css @@ -0,0 +1,10 @@ +.mat-sort-header-container { + align-items: center; +} + +sup { + font-size: 7px; + top: -3px; + position: absolute; + display: block; +} diff --git a/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.html b/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.html new file mode 100644 index 000000000000..1364cc45b7f5 --- /dev/null +++ b/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.html @@ -0,0 +1,25 @@ + + @if (!sortHeader.isDisabled && sortHeader.isSorted) { + {{ sortHeader.direction === 'desc' ? 'Z-A' : 'A-Z' }} + } + + + + + + + + + + + + @for (dessert of sortedData; track dessert) { + + + + + + + + } +
Dessert (100g)CaloriesFat (g)Carbs (g)Protein (g)
{{dessert.name}}{{dessert.calories}}{{dessert.fat}}{{dessert.carbs}}{{dessert.protein}}
diff --git a/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.ts b/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.ts new file mode 100644 index 000000000000..5c7b23b85c10 --- /dev/null +++ b/src/components-examples/material/sort/sort-header-custom/sort-header-custom-example.ts @@ -0,0 +1,65 @@ +import {Component} from '@angular/core'; +import {Sort, MatSortModule} from '@angular/material/sort'; + +export interface Dessert { + calories: number; + carbs: number; + fat: number; + name: string; + protein: number; +} + +/** + * @title Sorting header with custom template. + */ +@Component({ + selector: 'sort-header-custom-example', + templateUrl: 'sort-header-custom-example.html', + styleUrl: 'sort-header-custom-example.css', + imports: [MatSortModule], +}) +export class SortHeaderCustomExample { + desserts: Dessert[] = [ + {name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4}, + {name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4}, + {name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6}, + {name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4}, + {name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4}, + ]; + + sortedData: Dessert[]; + + constructor() { + this.sortedData = this.desserts.slice(); + } + + sortData(sort: Sort) { + const data = this.desserts.slice(); + if (!sort.active || sort.direction === '') { + this.sortedData = data; + return; + } + + this.sortedData = data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + switch (sort.active) { + case 'name': + return compare(a.name, b.name, isAsc); + case 'calories': + return compare(a.calories, b.calories, isAsc); + case 'fat': + return compare(a.fat, b.fat, isAsc); + case 'carbs': + return compare(a.carbs, b.carbs, isAsc); + case 'protein': + return compare(a.protein, b.protein, isAsc); + default: + return 0; + } + }); + } +} + +function compare(a: number | string, b: number | string, isAsc: boolean) { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); +} diff --git a/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.css b/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.css new file mode 100644 index 000000000000..5d2cd6d6ab47 --- /dev/null +++ b/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.css @@ -0,0 +1,3 @@ +.mat-sort-header-container { + align-items: center; +} diff --git a/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.html b/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.html new file mode 100644 index 000000000000..94591483711a --- /dev/null +++ b/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.html @@ -0,0 +1,19 @@ + + + + + + + + + + @for (dessert of sortedData; track dessert) { + + + + + + + + } +
Dessert (100g)CaloriesFat (g)Carbs (g)Protein (g)
{{dessert.name}}{{dessert.calories}}{{dessert.fat}}{{dessert.carbs}}{{dessert.protein}}
diff --git a/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.ts b/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.ts new file mode 100644 index 000000000000..566508199921 --- /dev/null +++ b/src/components-examples/material/sort/sort-header-icons-with-default/sort-header-icons-with-default-example.ts @@ -0,0 +1,77 @@ +import {Component} from '@angular/core'; +import {Sort, MatSortModule, MAT_SORT_DEFAULT_OPTIONS} from '@angular/material/sort'; + +export interface Dessert { + calories: number; + carbs: number; + fat: number; + name: string; + protein: number; +} + +/** + * @title Sorting header with configured default icons. + */ +@Component({ + selector: 'sort-header-icons-with-default-example', + templateUrl: 'sort-header-icons-with-default-example.html', + styleUrl: 'sort-header-icons-with-default-example.css', + imports: [MatSortModule], + providers: [ + { + provide: MAT_SORT_DEFAULT_OPTIONS, + useValue: { + icons: { + ascending: 'keyboard_arrow_up', + descending: 'keyboard_arrow_down', + default: 'unfold_more', // if you'd like to have always visible default sorting icon + }, + }, + }, + ], +}) +export class SortHeaderIconsWithDefaultExample { + desserts: Dessert[] = [ + {name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4}, + {name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4}, + {name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6}, + {name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4}, + {name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4}, + ]; + + sortedData: Dessert[]; + + constructor() { + this.sortedData = this.desserts.slice(); + } + + sortData(sort: Sort) { + const data = this.desserts.slice(); + if (!sort.active || sort.direction === '') { + this.sortedData = data; + return; + } + + this.sortedData = data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + switch (sort.active) { + case 'name': + return compare(a.name, b.name, isAsc); + case 'calories': + return compare(a.calories, b.calories, isAsc); + case 'fat': + return compare(a.fat, b.fat, isAsc); + case 'carbs': + return compare(a.carbs, b.carbs, isAsc); + case 'protein': + return compare(a.protein, b.protein, isAsc); + default: + return 0; + } + }); + } +} + +function compare(a: number | string, b: number | string, isAsc: boolean) { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); +} diff --git a/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.css b/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.css new file mode 100644 index 000000000000..5d2cd6d6ab47 --- /dev/null +++ b/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.css @@ -0,0 +1,3 @@ +.mat-sort-header-container { + align-items: center; +} diff --git a/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.html b/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.html new file mode 100644 index 000000000000..94591483711a --- /dev/null +++ b/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.html @@ -0,0 +1,19 @@ + + + + + + + + + + @for (dessert of sortedData; track dessert) { + + + + + + + + } +
Dessert (100g)CaloriesFat (g)Carbs (g)Protein (g)
{{dessert.name}}{{dessert.calories}}{{dessert.fat}}{{dessert.carbs}}{{dessert.protein}}
diff --git a/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.ts b/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.ts new file mode 100644 index 000000000000..66761c6738af --- /dev/null +++ b/src/components-examples/material/sort/sort-header-icons/sort-header-icons-example.ts @@ -0,0 +1,76 @@ +import {Component} from '@angular/core'; +import {Sort, MatSortModule, MAT_SORT_DEFAULT_OPTIONS} from '@angular/material/sort'; + +export interface Dessert { + calories: number; + carbs: number; + fat: number; + name: string; + protein: number; +} + +/** + * @title Sorting header with configured icons. + */ +@Component({ + selector: 'sort-header-icons-example', + templateUrl: 'sort-header-icons-example.html', + styleUrl: 'sort-header-icons-example.css', + imports: [MatSortModule], + providers: [ + { + provide: MAT_SORT_DEFAULT_OPTIONS, + useValue: { + icons: { + ascending: 'keyboard_arrow_up', + descending: 'keyboard_arrow_down', + }, + }, + }, + ], +}) +export class SortHeaderIconsExample { + desserts: Dessert[] = [ + {name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4}, + {name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4}, + {name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6}, + {name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4}, + {name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4}, + ]; + + sortedData: Dessert[]; + + constructor() { + this.sortedData = this.desserts.slice(); + } + + sortData(sort: Sort) { + const data = this.desserts.slice(); + if (!sort.active || sort.direction === '') { + this.sortedData = data; + return; + } + + this.sortedData = data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + switch (sort.active) { + case 'name': + return compare(a.name, b.name, isAsc); + case 'calories': + return compare(a.calories, b.calories, isAsc); + case 'fat': + return compare(a.fat, b.fat, isAsc); + case 'carbs': + return compare(a.carbs, b.carbs, isAsc); + case 'protein': + return compare(a.protein, b.protein, isAsc); + default: + return 0; + } + }); + } +} + +function compare(a: number | string, b: number | string, isAsc: boolean) { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); +} diff --git a/src/material/sort/BUILD.bazel b/src/material/sort/BUILD.bazel index 8664d123aa19..1923c635678c 100644 --- a/src/material/sort/BUILD.bazel +++ b/src/material/sort/BUILD.bazel @@ -69,6 +69,7 @@ ng_project( "sort-direction.ts", "sort-errors.ts", "sort-header.ts", + "sort-header-icons.ts", "sort-header-intl.ts", "sort-module.ts", ], @@ -77,12 +78,14 @@ ng_project( ":css", ], deps = [ + "//:node_modules/@angular/common", "//:node_modules/@angular/core", "//:node_modules/rxjs", "//src:dev_mode_types", "//src/cdk/a11y", "//src/cdk/keycodes", "//src/material/core", + "//src/material/icon", ], ) diff --git a/src/material/sort/public-api.ts b/src/material/sort/public-api.ts index a53962429a28..bb3d800f3de8 100644 --- a/src/material/sort/public-api.ts +++ b/src/material/sort/public-api.ts @@ -9,5 +9,6 @@ export * from './sort-module'; export * from './sort-direction'; export * from './sort-header'; +export * from './sort-header-icons'; export * from './sort-header-intl'; export * from './sort'; diff --git a/src/material/sort/sort-header-icons.ts b/src/material/sort/sort-header-icons.ts new file mode 100644 index 000000000000..68ed9062c888 --- /dev/null +++ b/src/material/sort/sort-header-icons.ts @@ -0,0 +1,17 @@ +/** + * @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 + */ + +/** Configuration object for customizing the icons used by `mat-sort-header`. */ +export interface SortHeaderIcons { + /** Icon shown when the column is not sorted (idle state). */ + default?: string; + /** Icon shown when the column is sorted ascending. */ + ascending: string; + /** Icon shown when the column is sorted descending. */ + descending: string; +} diff --git a/src/material/sort/sort-header.html b/src/material/sort/sort-header.html index 6f119af50223..b182e317c651 100644 --- a/src/material/sort/sort-header.html +++ b/src/material/sort/sort-header.html @@ -11,8 +11,9 @@
- - @if (_renderArrow()) { -
- + + @if ((!_isDisabled() && !defaultIcons?.default) || _isSorted()) { + + } + @else if (!_isSorted() && defaultIcons?.default) { + + } + + + @if (sortIconsTemplate() || defaultIcons) { +
+ +
} + @else { + + @if (_renderArrow()) { +
+ +
+ } + }
diff --git a/src/material/sort/sort-header.scss b/src/material/sort/sort-header.scss index c5ecfdae4500..faaba466ad3f 100644 --- a/src/material/sort/sort-header.scss +++ b/src/material/sort/sort-header.scss @@ -70,6 +70,54 @@ $fallbacks: m3-sort.get-tokens(); } } +.mat-sort-header-icon-slot { + $timing: 225ms cubic-bezier(0.4, 0, 0.2, 1); + width: 12px; + height: 12px; + position: relative; + transition: opacity $timing; + opacity: 0; + overflow: visible; + color: token-utils.slot(sort-arrow-color, $fallbacks); + + // stylelint-disable max-line-length + .mat-sort-header.cdk-keyboard-focused .mat-sort-header-container:not(.mat-sort-header-has-idle-icon) &, + .mat-sort-header.cdk-program-focused .mat-sort-header-container:not(.mat-sort-header-has-idle-icon) &, + .mat-sort-header:hover .mat-sort-header-container:not(.mat-sort-header-has-idle-icon):not(.mat-sort-header-sorted) & { + opacity: 0.54; + } + // stylelint-enable max-line-length + + .mat-sort-header-container.mat-sort-header-has-idle-icon &, + .mat-sort-header:not(.mat-sort-header-has-idle-icon) .mat-sort-header-sorted & { + opacity: 1; + } + + .mat-sort-header-animations-disabled & { + transition-duration: 0ms; + } + + & > mat-icon { + width: 24px; + height: 24px; + fill: currentColor; + position: absolute; + top: 50%; + left: 50%; + margin: -12px 0 0 -12px; + } + + &, + [dir='rtl'] .mat-sort-header-position-before & { + margin: 0 0 0 6px; + } + + .mat-sort-header-position-before &, + [dir='rtl'] & { + margin: 0 6px 0 0; + } +} + .mat-sort-header-arrow { $timing: 225ms cubic-bezier(0.4, 0, 0.2, 1); height: 12px; diff --git a/src/material/sort/sort-header.ts b/src/material/sort/sort-header.ts index 0ca8f6b13d1b..2c2a828bad36 100644 --- a/src/material/sort/sort-header.ts +++ b/src/material/sort/sort-header.ts @@ -21,7 +21,10 @@ import { inject, signal, ChangeDetectorRef, + TemplateRef, + input, } from '@angular/core'; +import {CommonModule} from '@angular/common'; import {merge, Subscription} from 'rxjs'; import { MAT_SORT_DEFAULT_OPTIONS, @@ -35,6 +38,7 @@ import {getSortHeaderNotContainedWithinSortError} from './sort-errors'; import {MatSortHeaderIntl} from './sort-header-intl'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; import {_animationsDisabled, _StructuralStylesLoader} from '../core'; +import {MatIcon} from '../icon'; /** * Valid positions for the arrow to be in for its opacity and translation. If the state is a @@ -89,6 +93,7 @@ interface MatSortHeaderColumnDef { }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, MatIcon], }) export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewInit { _intl = inject(MatSortHeaderIntl); @@ -96,10 +101,11 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI _columnDef = inject('MAT_SORT_HEADER_COLUMN_DEF' as any, { optional: true, }); - private _changeDetectorRef = inject(ChangeDetectorRef); - private _focusMonitor = inject(FocusMonitor); - private _elementRef = inject>(ElementRef); - private _ariaDescriber = inject(AriaDescriber, {optional: true}); + private readonly _changeDetectorRef = inject(ChangeDetectorRef); + private readonly _focusMonitor = inject(FocusMonitor); + private readonly _elementRef = inject>(ElementRef); + private readonly _ariaDescriber = inject(AriaDescriber, {optional: true}); + private readonly _defaultOptions = inject(MAT_SORT_DEFAULT_OPTIONS, {optional: true}); private _renderChanges: Subscription | undefined; protected _animationsDisabled = _animationsDisabled(); @@ -151,6 +157,19 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI @Input({transform: booleanAttribute}) disableClear: boolean; + /** + * Template for the sort icons. + * `{ isDisabled: boolean; direction: 'asc' | 'desc' | ''; isSorted: boolean }` passed as context. + */ + readonly sortIconsTemplate = input | null>(null, { + alias: 'matSortIconsTemplate', + }); + + /** Icons used by `mat-sort-header`. */ + get defaultIcons() { + return this._defaultOptions?.icons; + } + constructor(...args: unknown[]); constructor() { diff --git a/src/material/sort/sort.ts b/src/material/sort/sort.ts index b29325dbb65f..818d438ed6a6 100644 --- a/src/material/sort/sort.ts +++ b/src/material/sort/sort.ts @@ -26,6 +26,7 @@ import { getSortHeaderMissingIdError, getSortInvalidDirectionError, } from './sort-errors'; +import {SortHeaderIcons} from './sort-header-icons'; /** Position of the arrow that displays when sorted. */ export type SortHeaderArrowPosition = 'before' | 'after'; @@ -57,6 +58,11 @@ export interface MatSortDefaultOptions { disableClear?: boolean; /** Position of the arrow that displays when sorted. */ arrowPosition?: SortHeaderArrowPosition; + /** + * Icons used for the sort indicator. + * Optional - unspecified ones fall back to the framework default arrow. + */ + icons?: SortHeaderIcons; } /** Injection token to be used to override the default options for `mat-sort`. */