From 8efab8b621ea0b2aefe0fab8e10a02330dbd957b Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Mon, 3 Mar 2025 11:58:10 +0100 Subject: [PATCH 01/13] fix(material/tree): Remove `any` type --- src/material/tree/node.ts | 2 +- .../tree/tree-using-tree-control.spec.ts | 23 +++++++++++-------- src/material/tree/tree.spec.ts | 23 +++++++++++-------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/material/tree/node.ts b/src/material/tree/node.ts index ef7c17a1d970..d88c18db27e1 100644 --- a/src/material/tree/node.ts +++ b/src/material/tree/node.ts @@ -31,7 +31,7 @@ import {NoopTreeKeyManager, TreeKeyManagerItem, TreeKeyManagerStrategy} from '@a function isNoopTreeKeyManager( keyManager: TreeKeyManagerStrategy, ): keyManager is NoopTreeKeyManager { - return !!(keyManager as any)._isNoopTreeKeyManager; + return !!(keyManager as NoopTreeKeyManager)._isNoopTreeKeyManager; } /** diff --git a/src/material/tree/tree-using-tree-control.spec.ts b/src/material/tree/tree-using-tree-control.spec.ts index 2bb9875a4b73..a3c61e706103 100644 --- a/src/material/tree/tree-using-tree-control.spec.ts +++ b/src/material/tree/tree-using-tree-control.spec.ts @@ -18,14 +18,17 @@ import { MatTreeNestedDataSource, } from './index'; +type NodeContent = string[]; +type TreeContent = NodeContent[]; + describe('MatTree', () => { /** Represents an indent for expectNestedTreeToMatch */ - const _ = {}; + const _ = ''; let treeElement: HTMLElement; let underlyingDataSource: FakeDataSource; - function configureMatTreeTestingModule(declarations: Type[]) { + function configureMatTreeTestingModule(declarations: Type[]) { TestBed.configureTestingModule({ imports: [MatTreeModule], declarations: declarations, @@ -786,11 +789,11 @@ function getNodes(treeElement: Element): HTMLElement[] { function expectFlatTreeToMatch( treeElement: Element, expectedPaddingIndent: number = 28, - ...expectedTree: any[] + ...expectedTree: TreeContent ) { const missedExpectations: string[] = []; - function checkNode(node: Element, expectedNode: any[]) { + function checkNode(node: Element, expectedNode: NodeContent) { const actualTextContent = node.textContent!.trim(); const expectedTextContent = expectedNode[expectedNode.length - 1]; if (actualTextContent !== expectedTextContent) { @@ -800,7 +803,7 @@ function expectFlatTreeToMatch( } } - function checkLevel(node: Element, expectedNode: any[]) { + function checkLevel(node: Element, expectedNode: NodeContent) { const rawLevel = (node as HTMLElement).style.paddingLeft; // Some browsers return 0, while others return 0px. @@ -820,7 +823,7 @@ function expectFlatTreeToMatch( } getNodes(treeElement).forEach((node, index) => { - const expected = expectedTree ? expectedTree[index] : null; + const expected = expectedTree[index]; checkLevel(node, expected); checkNode(node, expected); @@ -831,9 +834,9 @@ function expectFlatTreeToMatch( } } -function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { +function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: TreeContent) { const missedExpectations: string[] = []; - function checkNodeContent(node: Element, expectedNode: any[]) { + function checkNodeContent(node: Element, expectedNode: NodeContent) { const expectedTextContent = expectedNode[expectedNode.length - 1]; const actualTextContent = node.childNodes.item(0).textContent!.trim(); if (actualTextContent !== expectedTextContent) { @@ -843,7 +846,7 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { } } - function checkNodeDescendants(node: Element, expectedNode: any[], currentIndex: number) { + function checkNodeDescendants(node: Element, expectedNode: NodeContent, currentIndex: number) { let expectedDescendant = 0; for (let i = currentIndex + 1; i < expectedTree.length; ++i) { @@ -863,7 +866,7 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { } getNodes(treeElement).forEach((node, index) => { - const expected = expectedTree ? expectedTree[index] : null; + const expected = expectedTree[index]; checkNodeDescendants(node, expected, index); checkNodeContent(node, expected); diff --git a/src/material/tree/tree.spec.ts b/src/material/tree/tree.spec.ts index 1a62d6b1505b..72e156d29873 100644 --- a/src/material/tree/tree.spec.ts +++ b/src/material/tree/tree.spec.ts @@ -11,14 +11,17 @@ import {BehaviorSubject, Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import {MatTree, MatTreeModule, MatTreeNestedDataSource} from './index'; +type NodeContent = string[]; +type TreeContent = NodeContent[]; + describe('MatTree', () => { /** Represents an indent for expectNestedTreeToMatch */ - const _ = {}; + const _ = ''; let treeElement: HTMLElement; let underlyingDataSource: FakeDataSource; - function configureMatTreeTestingModule(declarations: Type[]) { + function configureMatTreeTestingModule(declarations: Type[]) { TestBed.configureTestingModule({ imports: [MatTreeModule], declarations: declarations, @@ -785,11 +788,11 @@ function getNodes(treeElement: Element): HTMLElement[] { function expectFlatTreeToMatch( treeElement: Element, expectedPaddingIndent: number = 28, - ...expectedTree: any[] + ...expectedTree: TreeContent ) { const missedExpectations: string[] = []; - function checkNode(node: Element, expectedNode: any[]) { + function checkNode(node: Element, expectedNode: NodeContent) { const actualTextContent = node.textContent!.trim(); const expectedTextContent = expectedNode[expectedNode.length - 1]; if (actualTextContent !== expectedTextContent) { @@ -799,7 +802,7 @@ function expectFlatTreeToMatch( } } - function checkLevel(node: Element, expectedNode: any[]) { + function checkLevel(node: Element, expectedNode: NodeContent) { const rawLevel = (node as HTMLElement).style.paddingLeft; // Some browsers return 0, while others return 0px. @@ -819,7 +822,7 @@ function expectFlatTreeToMatch( } getNodes(treeElement).forEach((node, index) => { - const expected = expectedTree ? expectedTree[index] : null; + const expected = expectedTree[index]; checkLevel(node, expected); checkNode(node, expected); @@ -830,9 +833,9 @@ function expectFlatTreeToMatch( } } -function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { +function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: TreeContent) { const missedExpectations: string[] = []; - function checkNodeContent(node: Element, expectedNode: any[]) { + function checkNodeContent(node: Element, expectedNode: NodeContent) { const expectedTextContent = expectedNode[expectedNode.length - 1]; const actualTextContent = node.childNodes.item(0).textContent!.trim(); if (actualTextContent !== expectedTextContent) { @@ -842,7 +845,7 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { } } - function checkNodeDescendants(node: Element, expectedNode: any[], currentIndex: number) { + function checkNodeDescendants(node: Element, expectedNode: NodeContent, currentIndex: number) { let expectedDescendant = 0; for (let i = currentIndex + 1; i < expectedTree.length; ++i) { @@ -862,7 +865,7 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { } getNodes(treeElement).forEach((node, index) => { - const expected = expectedTree ? expectedTree[index] : null; + const expected = expectedTree[index]; checkNodeDescendants(node, expected, index); checkNodeContent(node, expected); From 42cf94a53f051639b3a9e615eb7fe4eef455e65a Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Mon, 3 Mar 2025 15:43:07 +0100 Subject: [PATCH 02/13] fix(material/tooltip): Remove almost all `any` --- src/material/tooltip/tooltip.spec.ts | 34 ++++++++++++++++------------ src/material/tooltip/tooltip.ts | 27 +++++++++++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/material/tooltip/tooltip.spec.ts b/src/material/tooltip/tooltip.spec.ts index 6f11bfd899c8..a70d62452ce4 100644 --- a/src/material/tooltip/tooltip.spec.ts +++ b/src/material/tooltip/tooltip.spec.ts @@ -1,7 +1,13 @@ import {FocusMonitor} from '@angular/cdk/a11y'; import {Direction, Directionality} from '@angular/cdk/bidi'; import {ESCAPE} from '@angular/cdk/keycodes'; -import {CdkScrollable, OverlayContainer, OverlayModule} from '@angular/cdk/overlay'; +import { + CdkScrollable, + ConnectedPosition, + FlexibleConnectedPositionStrategy, + OverlayContainer, + OverlayModule, +} from '@angular/cdk/overlay'; import {Platform} from '@angular/cdk/platform'; import { createFakeEvent, @@ -1414,7 +1420,7 @@ describe('MatTooltip', () => { fixture.detectChanges(); const styles = fixture.nativeElement.querySelector('button').style; - expect(styles.touchAction || (styles as any).webkitUserDrag).toBe('none'); + expect(styles.touchAction || styles.webkitUserDrag).toBe('none'); }); it('should allow native touch interactions if touch gestures are turned off', () => { @@ -1423,7 +1429,7 @@ describe('MatTooltip', () => { fixture.detectChanges(); const styles = fixture.nativeElement.querySelector('button').style; - expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); + expect(styles.touchAction || styles.webkitUserDrag).toBeFalsy(); }); it('should allow text selection on inputs when gestures are set to auto', () => { @@ -1435,13 +1441,13 @@ describe('MatTooltip', () => { expect(inputStyle.userSelect).toBeFalsy(); expect(inputStyle.webkitUserSelect).toBeFalsy(); - expect((inputStyle as any).msUserSelect).toBeFalsy(); - expect((inputStyle as any).MozUserSelect).toBeFalsy(); + expect(inputStyle.msUserSelect).toBeFalsy(); + expect(inputStyle.MozUserSelect).toBeFalsy(); expect(textareaStyle.userSelect).toBeFalsy(); expect(textareaStyle.webkitUserSelect).toBeFalsy(); - expect((textareaStyle as any).msUserSelect).toBeFalsy(); - expect((textareaStyle as any).MozUserSelect).toBeFalsy(); + expect(textareaStyle.msUserSelect).toBeFalsy(); + expect(textareaStyle.MozUserSelect).toBeFalsy(); }); it('should disable text selection on inputs when gestures are set to on', () => { @@ -1453,14 +1459,14 @@ describe('MatTooltip', () => { const inputUserSelect = inputStyle.userSelect || inputStyle.webkitUserSelect || - (inputStyle as any).msUserSelect || - (inputStyle as any).MozUserSelect; + inputStyle.msUserSelect || + inputStyle.MozUserSelect; const textareaStyle = fixture.componentInstance.textarea.nativeElement.style; const textareaUserSelect = textareaStyle.userSelect || textareaStyle.webkitUserSelect || - (textareaStyle as any).msUserSelect || - (textareaStyle as any).MozUserSelect; + textareaStyle.msUserSelect || + textareaStyle.MozUserSelect; expect(inputUserSelect).toBe('none'); expect(textareaUserSelect).toBe('none'); @@ -1599,7 +1605,7 @@ describe('MatTooltip', () => { }) class BasicTooltipDemo { position = 'below'; - message: any = initialTooltipMessage; + message: string | number = initialTooltipMessage; showButton = true; showTooltipClass = false; tooltipDisabled = false; @@ -1716,7 +1722,7 @@ class TooltipOnDraggableElement { standalone: false, }) class TooltipDemoWithoutPositionBinding { - message: any = initialTooltipMessage; + message: string = initialTooltipMessage; @ViewChild(MatTooltip) tooltip: MatTooltip; @ViewChild('button') button: ElementRef; } @@ -1740,7 +1746,7 @@ class TooltipDemoWithoutTooltipClassBinding { standalone: false, }) class TooltipDemoWithTooltipClassBinding { - message: any = initialTooltipMessage; + message: string = initialTooltipMessage; @ViewChild(MatTooltip) tooltip: MatTooltip; @ViewChild('button') button: ElementRef; } diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index ccd75286af37..239b677a8e2f 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -52,6 +52,15 @@ import {ComponentPortal} from '@angular/cdk/portal'; import {Observable, Subject} from 'rxjs'; import {_animationsDisabled} from '../core'; +declare global { + interface CSSStyleDeclaration { + msUserSelect: string; + MozUserSelect: string; + webkitUserDrag: string; + webkitTapHighlightColor: string; + } +} + /** Possible positions for a tooltip. */ export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after'; @@ -219,7 +228,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { private _position: TooltipPosition = 'below'; private _positionAtOrigin: boolean = false; private _disabled: boolean = false; - private _tooltipClass: string | string[] | Set | {[key: string]: any}; + private _tooltipClass: string | string[] | Set | {[key: string]: unknown}; private _viewInitialized = false; private _pointerExitEventsInitialized = false; private readonly _tooltipComponent = TooltipComponent; @@ -361,7 +370,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { return this._tooltipClass; } - set tooltipClass(value: string | string[] | Set | {[key: string]: any}) { + set tooltipClass(value: string | string[] | Set | {[key: string]: unknown}) { this._tooltipClass = value; if (this._tooltipInstance) { this._setTooltipClass(this._tooltipClass); @@ -723,7 +732,9 @@ export class MatTooltip implements OnDestroy, AfterViewInit { } /** Updates the tooltip class */ - private _setTooltipClass(tooltipClass: string | string[] | Set | {[key: string]: any}) { + private _setTooltipClass( + tooltipClass: string | string[] | Set | {[key: string]: unknown}, + ) { if (this._tooltipInstance) { this._tooltipInstance.tooltipClass = tooltipClass; this._tooltipInstance._markForCheck(); @@ -911,20 +922,20 @@ export class MatTooltip implements OnDestroy, AfterViewInit { // textareas, because it prevents the user from typing into them on iOS Safari. if (gestures === 'on' || (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA')) { style.userSelect = - (style as any).msUserSelect = + style.msUserSelect = style.webkitUserSelect = - (style as any).MozUserSelect = + style.MozUserSelect = 'none'; } // If we have `auto` gestures and the element uses native HTML dragging, // we don't set `-webkit-user-drag` because it prevents the native behavior. if (gestures === 'on' || !element.draggable) { - (style as any).webkitUserDrag = 'none'; + style.webkitUserDrag = 'none'; } style.touchAction = 'none'; - (style as any).webkitTapHighlightColor = 'transparent'; + style.webkitTapHighlightColor = 'transparent'; } } @@ -985,7 +996,7 @@ export class TooltipComponent implements OnDestroy { message: string; /** Classes to be added to the tooltip. Supports the same syntax as `ngClass`. */ - tooltipClass: string | string[] | Set | {[key: string]: any}; + tooltipClass: string | string[] | Set | {[key: string]: unknown}; /** The timeout ID of any current timer set to show the tooltip */ private _showTimeoutId: ReturnType | undefined; From 2bfbc9fc628de049539e79c94dfcc21db1998c0b Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Mon, 3 Mar 2025 16:05:09 +0100 Subject: [PATCH 03/13] fix(material/timepicker): Replace `any` with `unknown` in timepicker-input --- src/material/timepicker/timepicker-input.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/material/timepicker/timepicker-input.ts b/src/material/timepicker/timepicker-input.ts index 3912a236e2d8..3a4fcd4c7e08 100644 --- a/src/material/timepicker/timepicker-input.ts +++ b/src/material/timepicker/timepicker-input.ts @@ -86,7 +86,7 @@ export class MatTimepickerInput implements ControlValueAccessor, Validator, O private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!; private _formField = inject(MAT_FORM_FIELD, {optional: true}); - private _onChange: ((value: any) => void) | undefined; + private _onChange: ((value: unknown) => void) | undefined; private _onTouched: (() => void) | undefined; private _validatorOnChange: (() => void) | undefined; private _cleanupClick: () => void; @@ -183,7 +183,7 @@ export class MatTimepickerInput implements ControlValueAccessor, Validator, O * Implemented as a part of `ControlValueAccessor`. * @docs-private */ - writeValue(value: any): void { + writeValue(value: unknown): void { // Note that we need to deserialize here, rather than depend on the value change effect, // because `getValidDateOrNull` will clobber the value if it's parseable, but not created by // the current adapter (see #30140). @@ -195,7 +195,7 @@ export class MatTimepickerInput implements ControlValueAccessor, Validator, O * Implemented as a part of `ControlValueAccessor`. * @docs-private */ - registerOnChange(fn: (value: any) => void): void { + registerOnChange(fn: (value: unknown) => void): void { this._onChange = fn; } From deb77e2e376eb178a0a9ab0f9d20effecaf5b030 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Mon, 3 Mar 2025 16:50:11 +0100 Subject: [PATCH 04/13] fix(material/table): Remove any Also added new error throwing logic inside table-data-source when data is not an object. --- src/material/table/table-data-source.ts | 6 ++++- src/material/table/table.spec.ts | 34 ++++++++++++------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/material/table/table-data-source.ts b/src/material/table/table-data-source.ts index c19e8e1e921a..debfe0b71933 100644 --- a/src/material/table/table-data-source.ts +++ b/src/material/table/table-data-source.ts @@ -229,10 +229,14 @@ export class MatTableDataSource extend * @returns Whether the filter matches against the data */ filterPredicate: (data: T, filter: string) => boolean = (data: T, filter: string): boolean => { + if ((typeof ngDevMode === 'undefined' || ngDevMode) && typeof data !== 'object') { + throw new Error('Default implementation of filterPredicate requires data to be object.'); + } + // Transform the filter by converting it to lowercase and removing whitespace. const transformedFilter = filter.trim().toLowerCase(); // Loops over the values in the array and returns true if any of them match the filter string - return Object.values(data as {[key: string]: any}).some(value => + return Object.values(data as object).some(value => `${value}`.toLowerCase().includes(transformedFilter), ); }; diff --git a/src/material/table/table.spec.ts b/src/material/table/table.spec.ts index 20debc1c261c..d6ea27fb715a 100644 --- a/src/material/table/table.spec.ts +++ b/src/material/table/table.spec.ts @@ -43,9 +43,9 @@ describe('MatTable', () => { const data = fixture.componentInstance.dataSource!.data; expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - [data[0].a, data[0].b, data[0].c], - [data[1].a, data[1].b, data[1].c], - [data[2].a, data[2].b, data[2].c], + [data[0].a, data[0].b, data[0].c] as string[], + [data[1].a, data[1].b, data[1].c] as string[], + [data[2].a, data[2].b, data[2].c] as string[], ['fourth_row'], ['Footer A', 'Footer B', 'Footer C'], ]); @@ -91,10 +91,10 @@ describe('MatTable', () => { const data = fixture.componentInstance.dataSource!.data; expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - [data[0].a, data[0].b, data[0].c], - [data[1].a, data[1].b, data[1].c], - [data[2].a, data[2].b, data[2].c], - [data[3].a, data[3].b, data[3].c], + [data[0].a, data[0].b, data[0].c] as string[], + [data[1].a, data[1].b, data[1].c] as string[], + [data[2].a, data[2].b, data[2].c] as string[], + [data[3].a, data[3].b, data[3].c] as string[], ]); }); @@ -186,9 +186,9 @@ describe('MatTable', () => { const data = fixture.componentInstance.dataSource!.data; expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - [data[0].a, data[0].b, data[0].c], - [data[1].a, data[1].b, data[1].c], - [data[2].a, data[2].b, data[2].c], + [data[0].a, data[0].b, data[0].c] as string[], + [data[1].a, data[1].b, data[1].c] as string[], + [data[2].a, data[2].b, data[2].c] as string[], ]); }); @@ -200,9 +200,9 @@ describe('MatTable', () => { const data = fixture.componentInstance.dataSource!.data; expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - [data[0].a, data[0].b, data[0].c], - [data[1].a, data[1].b, data[1].c], - [data[2].a, data[2].b, data[2].c], + [data[0].a, data[0].b, data[0].c] as string[], + [data[1].a, data[1].b, data[1].c] as string[], + [data[2].a, data[2].b, data[2].c] as string[], ]); }); @@ -384,7 +384,7 @@ describe('MatTable', () => { ]); // Change the filter to a falsy value that might come in from the view. - dataSource.filter = 0 as any; + dataSource.filter = 0 as unknown as string; flushMicrotasks(); fixture.detectChanges(); expectTableToMatchContent(tableElement, [ @@ -631,7 +631,7 @@ describe('MatTable', () => { ['Footer A', 'Footer B', 'Footer C'], ]); - dataSource.data = {} as any; + dataSource.data = {} as TestData[]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expectTableToMatchContent(tableElement, [ @@ -1135,7 +1135,7 @@ function getActualTableContent(tableElement: Element): string[][] { return actualTableContent.map(row => row.map(cell => cell.textContent!.trim())); } -export function expectTableToMatchContent(tableElement: Element, expected: any[]) { +export function expectTableToMatchContent(tableElement: Element, expected: string[][]) { const missedExpectations: string[] = []; function checkCellContent(actualCell: string, expectedCell: string) { if (actualCell !== expectedCell) { @@ -1161,7 +1161,7 @@ export function expectTableToMatchContent(tableElement: Element, expected: any[] } row.forEach((actualCell, cellIndex) => { - const expectedCell = expectedRow ? expectedRow[cellIndex] : null; + const expectedCell = expectedRow[cellIndex]; checkCellContent(actualCell, expectedCell); }); }); From 7f71febc898b62629eadb08433dd2c79dc7d61b8 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Mon, 3 Mar 2025 17:43:13 +0100 Subject: [PATCH 05/13] fix(material/tabs): Remove `any` type --- src/material/tabs/tab-body.spec.ts | 2 +- src/material/tabs/tab-content.ts | 2 +- src/material/tabs/tab-group.spec.ts | 31 ++++++++++++++-------------- src/material/tabs/tab-header.spec.ts | 2 +- src/material/tabs/tab-label.ts | 3 ++- src/material/tabs/tab.ts | 9 ++++---- 6 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/material/tabs/tab-body.spec.ts b/src/material/tabs/tab-body.spec.ts index 08b8dc206ccf..432bf583bfa7 100644 --- a/src/material/tabs/tab-body.spec.ts +++ b/src/material/tabs/tab-body.spec.ts @@ -146,7 +146,7 @@ class SimpleTabBodyApp implements AfterViewInit { position: number; @ViewChild(MatTabBody) tabBody: MatTabBody; - @ViewChild(TemplateRef) template: TemplateRef; + @ViewChild(TemplateRef) template: TemplateRef; private readonly _viewContainerRef = inject(ViewContainerRef); diff --git a/src/material/tabs/tab-content.ts b/src/material/tabs/tab-content.ts index f148342fdad0..c27a74065185 100644 --- a/src/material/tabs/tab-content.ts +++ b/src/material/tabs/tab-content.ts @@ -21,7 +21,7 @@ export const MAT_TAB_CONTENT = new InjectionToken('MatTabContent' providers: [{provide: MAT_TAB_CONTENT, useExisting: MatTabContent}], }) export class MatTabContent { - template = inject>(TemplateRef); + template = inject>(TemplateRef); constructor(...args: unknown[]); constructor() {} diff --git a/src/material/tabs/tab-group.spec.ts b/src/material/tabs/tab-group.spec.ts index 785ab4452365..834e0069c50d 100644 --- a/src/material/tabs/tab-group.spec.ts +++ b/src/material/tabs/tab-group.spec.ts @@ -11,7 +11,7 @@ import { waitForAsync, } from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {Observable} from 'rxjs'; +import {Observable, Subscriber} from 'rxjs'; import { MAT_TABS_CONFIG, MatTab, @@ -19,6 +19,7 @@ import { MatTabHeader, MatTabHeaderPosition, MatTabsModule, + MatTabChangeEvent, } from './index'; import {MATERIAL_ANIMATIONS} from '../core'; @@ -1028,7 +1029,7 @@ describe('MatTabGroup', () => { * Checks that the `selectedIndex` has been updated; checks that the label and body have their * respective `active` classes */ - function checkSelectedIndex(expectedIndex: number, fixture: ComponentFixture) { + function checkSelectedIndex(expectedIndex: number, fixture: ComponentFixture) { fixture.detectChanges(); let tabComponent: MatTabGroup = fixture.debugElement.query( @@ -1047,11 +1048,11 @@ describe('MatTabGroup', () => { expect(tabContentElement.classList.contains('mat-mdc-tab-body-active')).toBe(true); } - function getSelectedLabel(fixture: ComponentFixture): HTMLElement { + function getSelectedLabel(fixture: ComponentFixture): HTMLElement { return fixture.nativeElement.querySelector('.mdc-tab--active'); } - function getSelectedContent(fixture: ComponentFixture): HTMLElement { + function getSelectedContent(fixture: ComponentFixture): HTMLElement { return fixture.nativeElement.querySelector('.mat-mdc-tab-body-active'); } }); @@ -1285,18 +1286,18 @@ class SimpleTabsTestApp { @ViewChild(MatTabGroup) tabGroup: MatTabGroup; @ViewChildren(MatTab) tabs: QueryList; selectedIndex: number = 1; - focusEvent: any; - selectEvent: any; + focusEvent: MatTabChangeEvent; + selectEvent: MatTabChangeEvent; disableRipple: boolean = false; contentTabIndex: number | null = null; headerPosition: MatTabHeaderPosition = 'above'; ariaLabel: string; ariaLabelledby: string; secondTabId: string | null = null; - handleFocus(event: any) { + handleFocus(event: MatTabChangeEvent) { this.focusEvent = event; } - handleSelection(event: any) { + handleSelection(event: MatTabChangeEvent) { this.selectEvent = event; } animationDone() {} @@ -1326,13 +1327,13 @@ class SimpleDynamicTabsTestApp { {label: 'Label 3', content: 'Content 3'}, ]; selectedIndex: number = 1; - focusEvent: any; - selectEvent: any; + focusEvent: MatTabChangeEvent; + selectEvent: MatTabChangeEvent; disablePagination = false; - handleFocus(event: any) { + handleFocus(event: MatTabChangeEvent) { this.focusEvent = event; } - handleSelection(event: any) { + handleSelection(event: MatTabChangeEvent) { this.selectEvent = event; } } @@ -1406,11 +1407,11 @@ class AsyncTabsTestApp implements OnInit { {label: 'two', content: 'two'}, ]; - tabs: Observable; + tabs: Observable; ngOnInit() { // Use ngOnInit because there is some issue with scheduling the async task in the constructor. - this.tabs = new Observable((observer: any) => { + this.tabs = new Observable((observer: Subscriber) => { setTimeout(() => observer.next(this._tabs)); }); } @@ -1431,7 +1432,7 @@ class TabGroupWithSimpleApi { preserveContent = false; otherLabel = 'Fruit'; otherContent = 'Apples, grapes'; - @ViewChild('legumes') legumes: any; + @ViewChild('legumes') legumes: HTMLParagraphElement; } @Component({ diff --git a/src/material/tabs/tab-header.spec.ts b/src/material/tabs/tab-header.spec.ts index 06705a3c6711..e4b603e23105 100644 --- a/src/material/tabs/tab-header.spec.ts +++ b/src/material/tabs/tab-header.spec.ts @@ -699,7 +699,7 @@ describe('MatTabHeader', () => { spyOn(TestBed.inject(MutationObserverFactory), 'create').and.callFake( (callback: Function) => { mutationCallbacks.push(callback); - return {observe: () => {}, disconnect: () => {}} as any; + return {observe: () => {}, disconnect: () => {}} as unknown as MutationObserver; }, ); diff --git a/src/material/tabs/tab-label.ts b/src/material/tabs/tab-label.ts index dff42617d012..ab9f8c5c1cc0 100644 --- a/src/material/tabs/tab-label.ts +++ b/src/material/tabs/tab-label.ts @@ -8,6 +8,7 @@ import {Directive, InjectionToken, inject} from '@angular/core'; import {CdkPortal} from '@angular/cdk/portal'; +import {type MatTab} from './tab'; /** * Injection token that can be used to reference instances of `MatTabLabel`. It serves as @@ -20,7 +21,7 @@ export const MAT_TAB_LABEL = new InjectionToken('MatTabLabel'); * Used to provide a tab label to a tab without causing a circular dependency. * @docs-private */ -export const MAT_TAB = new InjectionToken('MAT_TAB'); +export const MAT_TAB = new InjectionToken('MAT_TAB'); /** Used to flag tab labels for use with the portal directive */ @Directive({ diff --git a/src/material/tabs/tab.ts b/src/material/tabs/tab.ts index e54b106c610a..2288dc35509c 100644 --- a/src/material/tabs/tab.ts +++ b/src/material/tabs/tab.ts @@ -29,12 +29,13 @@ import {TemplatePortal} from '@angular/cdk/portal'; import {Subject} from 'rxjs'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; import {_StructuralStylesLoader} from '../core'; +import {type MatTabGroup} from './tab-group'; /** * Used to provide a tab group to a tab without causing a circular dependency. * @docs-private */ -export const MAT_TAB_GROUP = new InjectionToken('MAT_TAB_GROUP'); +export const MAT_TAB_GROUP = new InjectionToken('MAT_TAB_GROUP'); @Component({ selector: 'mat-tab', @@ -78,11 +79,11 @@ export class MatTab implements OnInit, OnChanges, OnDestroy { * Template provided in the tab content that will be used if present, used to enable lazy-loading */ @ContentChild(MatTabContent, {read: TemplateRef, static: true}) - // We need an initializer here to avoid a TS error. The value will be set in `ngAfterViewInit`. - private _explicitContent: TemplateRef = undefined!; + // The value will be set in `ngAfterViewInit`. + private _explicitContent?: TemplateRef; /** Template inside the MatTab view that contains an ``. */ - @ViewChild(TemplateRef, {static: true}) _implicitContent: TemplateRef; + @ViewChild(TemplateRef, {static: true}) _implicitContent: TemplateRef; /** Plain text label for the tab, used when there is no template label. */ @Input('label') textLabel: string = ''; From 9cc7ffaab9a358d3e9e6de035c15f8bebc463640 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Mon, 3 Mar 2025 18:13:27 +0100 Subject: [PATCH 06/13] fix(material/stepper): Replace `any` with `unknown` --- src/material/stepper/step-content.ts | 2 +- src/material/stepper/stepper.spec.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/material/stepper/step-content.ts b/src/material/stepper/step-content.ts index 8328c0d10f5e..1bec41580fb3 100644 --- a/src/material/stepper/step-content.ts +++ b/src/material/stepper/step-content.ts @@ -15,7 +15,7 @@ import {Directive, TemplateRef, inject} from '@angular/core'; selector: 'ng-template[matStepContent]', }) export class MatStepContent { - _template = inject>(TemplateRef); + _template = inject>(TemplateRef); constructor(...args: unknown[]); constructor() {} diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index 959e36d93a3e..81de52655345 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -1640,7 +1640,7 @@ describe('MatStepper', () => { /** Asserts that keyboard interaction works correctly. */ function assertCorrectKeyboardInteraction( - fixture: ComponentFixture, + fixture: ComponentFixture, stepHeaders: DebugElement[], orientation: StepperOrientation, ) { @@ -1741,7 +1741,7 @@ function assertCorrectKeyboardInteraction( /** Asserts that arrow key direction works correctly in RTL mode. */ function assertArrowKeyInteractionInRtl( - fixture: ComponentFixture, + fixture: ComponentFixture, stepHeaders: DebugElement[], ) { const stepperComponent = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance; @@ -1763,7 +1763,7 @@ function assertArrowKeyInteractionInRtl( /** Asserts that keyboard interaction works correctly when the user is pressing a modifier key. */ function assertSelectKeyWithModifierInteraction( - fixture: ComponentFixture, + fixture: ComponentFixture, stepHeaders: DebugElement[], orientation: StepperOrientation, selectionKey: number, @@ -1822,7 +1822,7 @@ function asyncValidator(minLength: number, validationTrigger: Subject): As function createComponent( component: Type, providers: Provider[] = [], - imports: any[] = [], + imports: unknown[] = [], encapsulation?: ViewEncapsulation, declarations = [component], ): ComponentFixture { From 2a991b1b66fbecf104278e40409ce2d104df3cd1 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Mon, 3 Mar 2025 18:24:59 +0100 Subject: [PATCH 07/13] fix(material/sort): Remove `any` type from sort tests --- src/material/sort/sort.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/material/sort/sort.spec.ts b/src/material/sort/sort.spec.ts index 1153f43ee5da..1a9e6e53b27f 100644 --- a/src/material/sort/sort.spec.ts +++ b/src/material/sort/sort.spec.ts @@ -525,8 +525,8 @@ class SimpleMatSortApp { } } -class FakeDataSource extends DataSource { - connect(collectionViewer: CollectionViewer): Observable { +class FakeDataSource extends DataSource { + connect(collectionViewer: CollectionViewer): Observable { return collectionViewer.viewChange.pipe(map(() => [])); } disconnect() {} From d8bf0ac0225cb3726dc06d90213c6ae8d8c0cbfd Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Mon, 3 Mar 2025 19:23:31 +0100 Subject: [PATCH 08/13] fix(materialsnack-bar): Replace `any` with `unknown` --- src/material/snack-bar/simple-snack-bar.ts | 10 ++++++-- src/material/snack-bar/snack-bar-config.ts | 4 ++-- src/material/snack-bar/snack-bar.spec.ts | 15 ++++++++---- src/material/snack-bar/snack-bar.ts | 27 +++++++++++++--------- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/material/snack-bar/simple-snack-bar.ts b/src/material/snack-bar/simple-snack-bar.ts index 09070641000e..d80d4f01cbce 100644 --- a/src/material/snack-bar/simple-snack-bar.ts +++ b/src/material/snack-bar/simple-snack-bar.ts @@ -12,11 +12,17 @@ import {MatSnackBarRef} from './snack-bar-ref'; import {MAT_SNACK_BAR_DATA} from './snack-bar-config'; import {MatSnackBarAction, MatSnackBarActions, MatSnackBarLabel} from './snack-bar-content'; +/** Input data for the snack bar. */ +export interface TextOnlySnackBarData { + message: string; + action: string; +} + /** * Interface for a simple snack bar component that has a message and a single action. */ export interface TextOnlySnackBar { - data: {message: string; action: string}; + data: TextOnlySnackBarData; snackBarRef: MatSnackBarRef; action: () => void; hasAction: boolean; @@ -36,7 +42,7 @@ export interface TextOnlySnackBar { }) export class SimpleSnackBar implements TextOnlySnackBar { snackBarRef = inject>(MatSnackBarRef); - data = inject(MAT_SNACK_BAR_DATA); + data = inject(MAT_SNACK_BAR_DATA); constructor(...args: unknown[]); constructor() {} diff --git a/src/material/snack-bar/snack-bar-config.ts b/src/material/snack-bar/snack-bar-config.ts index 3ea23b9d8255..89dbe61b157c 100644 --- a/src/material/snack-bar/snack-bar-config.ts +++ b/src/material/snack-bar/snack-bar-config.ts @@ -11,7 +11,7 @@ import {AriaLivePoliteness} from '@angular/cdk/a11y'; import {Direction} from '@angular/cdk/bidi'; /** Injection token that can be used to access the data that was passed in to a snack bar. */ -export const MAT_SNACK_BAR_DATA = new InjectionToken('MatSnackBarData'); +export const MAT_SNACK_BAR_DATA = new InjectionToken('MatSnackBarData'); /** Possible values for horizontalPosition on MatSnackBarConfig. */ export type MatSnackBarHorizontalPosition = 'start' | 'center' | 'end' | 'left' | 'right'; @@ -22,7 +22,7 @@ export type MatSnackBarVerticalPosition = 'top' | 'bottom'; /** * Configuration used when opening a snack-bar. */ -export class MatSnackBarConfig { +export class MatSnackBarConfig { /** The politeness level for the MatAriaLiveAnnouncer announcement. */ politeness?: AriaLivePoliteness = 'polite'; diff --git a/src/material/snack-bar/snack-bar.spec.ts b/src/material/snack-bar/snack-bar.spec.ts index 44de56f3084f..fd27882d7f46 100644 --- a/src/material/snack-bar/snack-bar.spec.ts +++ b/src/material/snack-bar/snack-bar.spec.ts @@ -593,10 +593,11 @@ describe('MatSnackBar', () => { }); it('should be able to inject arbitrary user data', () => { + const data: BurritosNotificationData = { + burritoType: 'Chimichanga', + }; const snackBarRef = snackBar.openFromComponent(BurritosNotification, { - data: { - burritoType: 'Chimichanga', - }, + data, }); expect(snackBarRef.instance.data) @@ -1013,17 +1014,21 @@ class ComponentWithChildViewContainer { `, }) class ComponentWithTemplateRef { - @ViewChild(TemplateRef) templateRef: TemplateRef; + @ViewChild(TemplateRef) templateRef: TemplateRef; localValue: string; } +interface BurritosNotificationData { + burritoType: string; +} + /** Simple component for testing ComponentPortal. */ @Component({ template: '

Burritos are on the way.

', }) class BurritosNotification { snackBarRef = inject>(MatSnackBarRef); - data = inject(MAT_SNACK_BAR_DATA); + data = inject(MAT_SNACK_BAR_DATA); } @Component({ diff --git a/src/material/snack-bar/snack-bar.ts b/src/material/snack-bar/snack-bar.ts index c7e65e3e2b83..ddbb3482f10c 100644 --- a/src/material/snack-bar/snack-bar.ts +++ b/src/material/snack-bar/snack-bar.ts @@ -63,7 +63,7 @@ export class MatSnackBar implements OnDestroy { * If there is a parent snack-bar service, all operations should delegate to that parent * via `_openedSnackBarRef`. */ - private _snackBarRefAtThisLevel: MatSnackBarRef | null = null; + private _snackBarRefAtThisLevel: MatSnackBarRef | null = null; /** The component that should be rendered as the snack bar's simple component. */ simpleSnackBarComponent = SimpleSnackBar; @@ -75,12 +75,12 @@ export class MatSnackBar implements OnDestroy { handsetCssClass = 'mat-mdc-snack-bar-handset'; /** Reference to the currently opened snackbar at *any* level. */ - get _openedSnackBarRef(): MatSnackBarRef | null { + get _openedSnackBarRef(): MatSnackBarRef | null { const parent = this._parentSnackBar; return parent ? parent._openedSnackBarRef : this._snackBarRefAtThisLevel; } - set _openedSnackBarRef(value: MatSnackBarRef | null) { + set _openedSnackBarRef(value: MatSnackBarRef | null) { if (this._parentSnackBar) { this._parentSnackBar._openedSnackBarRef = value; } else { @@ -98,11 +98,11 @@ export class MatSnackBar implements OnDestroy { * @param component Component to be instantiated. * @param config Extra configuration for the snack bar. */ - openFromComponent( + openFromComponent( component: ComponentType, config?: MatSnackBarConfig, ): MatSnackBarRef { - return this._attach(component, config) as MatSnackBarRef; + return this._attach(component, config); } /** @@ -113,9 +113,9 @@ export class MatSnackBar implements OnDestroy { * @param config Extra configuration for the snack bar. */ openFromTemplate( - template: TemplateRef, + template: TemplateRef, config?: MatSnackBarConfig, - ): MatSnackBarRef> { + ): MatSnackBarRef> { return this._attach(template, config); } @@ -187,14 +187,19 @@ export class MatSnackBar implements OnDestroy { /** * Places a new component or a template as the content of the snack bar container. */ + private _attach(content: ComponentType, userConfig?: MatSnackBarConfig): MatSnackBarRef; + private _attach( + content: TemplateRef, + userConfig?: MatSnackBarConfig, + ): MatSnackBarRef>; private _attach( content: ComponentType | TemplateRef, userConfig?: MatSnackBarConfig, - ): MatSnackBarRef> { + ): MatSnackBarRef> { const config = {...new MatSnackBarConfig(), ...this._defaultConfig, ...userConfig}; const overlayRef = this._createOverlay(config); const container = this._attachSnackBarContainer(overlayRef, config); - const snackBarRef = new MatSnackBarRef>(container, overlayRef); + const snackBarRef = new MatSnackBarRef>(container, overlayRef); if (content instanceof TemplateRef) { const portal = new TemplatePortal(content, null!, { @@ -231,11 +236,11 @@ export class MatSnackBar implements OnDestroy { this._animateSnackBar(snackBarRef, config); this._openedSnackBarRef = snackBarRef; - return this._openedSnackBarRef; + return snackBarRef; } /** Animates the old snack bar out and the new one in. */ - private _animateSnackBar(snackBarRef: MatSnackBarRef, config: MatSnackBarConfig) { + private _animateSnackBar(snackBarRef: MatSnackBarRef, config: MatSnackBarConfig) { // When the snackbar is dismissed, clear the reference to it. snackBarRef.afterDismissed().subscribe(() => { // Clear the snackbar ref if it hasn't already been replaced by a newer snackbar. From e731541b6135059e3063628e737e10907df69fb5 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 28 Mar 2025 18:44:57 +0100 Subject: [PATCH 09/13] refactor(material/tooltip): remove unused imports --- src/material/tooltip/tooltip.spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/material/tooltip/tooltip.spec.ts b/src/material/tooltip/tooltip.spec.ts index a70d62452ce4..357176ac6596 100644 --- a/src/material/tooltip/tooltip.spec.ts +++ b/src/material/tooltip/tooltip.spec.ts @@ -1,13 +1,7 @@ import {FocusMonitor} from '@angular/cdk/a11y'; import {Direction, Directionality} from '@angular/cdk/bidi'; import {ESCAPE} from '@angular/cdk/keycodes'; -import { - CdkScrollable, - ConnectedPosition, - FlexibleConnectedPositionStrategy, - OverlayContainer, - OverlayModule, -} from '@angular/cdk/overlay'; +import {CdkScrollable, OverlayContainer, OverlayModule} from '@angular/cdk/overlay'; import {Platform} from '@angular/cdk/platform'; import { createFakeEvent, From edf796515a5391b16d849f491d46c1da705295cc Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 11 Apr 2025 15:47:21 +0200 Subject: [PATCH 10/13] refactor(material/tabs): fix circular dependencies --- src/material/tabs/public-api.ts | 6 ++++-- src/material/tabs/tab-group-token.ts | 13 +++++++++++++ src/material/tabs/tab-group.ts | 3 ++- src/material/tabs/tab-label.ts | 8 +------- src/material/tabs/tab-token.ts | 13 +++++++++++++ src/material/tabs/tab.ts | 18 ++++++------------ 6 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 src/material/tabs/tab-group-token.ts create mode 100644 src/material/tabs/tab-token.ts diff --git a/src/material/tabs/public-api.ts b/src/material/tabs/public-api.ts index d7b4df8e9ba1..088ddc6291f4 100644 --- a/src/material/tabs/public-api.ts +++ b/src/material/tabs/public-api.ts @@ -16,8 +16,10 @@ export { } from './tab-body'; export {MatTabsConfig, MAT_TABS_CONFIG} from './tab-config'; export {MatTabContent, MAT_TAB_CONTENT} from './tab-content'; -export {MatTabLabel, MAT_TAB, MAT_TAB_LABEL} from './tab-label'; -export {MatTab, MAT_TAB_GROUP} from './tab'; +export {MatTabLabel, MAT_TAB_LABEL} from './tab-label'; +export {MatTab} from './tab'; +export {MAT_TAB, MatTabBase} from './tab-token'; +export {MAT_TAB_GROUP, MatTabGroupBase} from './tab-group-token'; export { MatInkBar, _MatInkBarPositioner, diff --git a/src/material/tabs/tab-group-token.ts b/src/material/tabs/tab-group-token.ts new file mode 100644 index 000000000000..b55cbeea6f6e --- /dev/null +++ b/src/material/tabs/tab-group-token.ts @@ -0,0 +1,13 @@ +import {InjectionToken} from '@angular/core'; + +/** + * Used to provide a tab group without causing a circular dependency. + * @docs-private + */ +export interface MatTabGroupBase {} + +/** + * Used to provide a tab group without causing a circular dependency. + * @docs-private + */ +export const MAT_TAB_GROUP = new InjectionToken('MAT_TAB_GROUP'); diff --git a/src/material/tabs/tab-group.ts b/src/material/tabs/tab-group.ts index c71f872f5245..8f32b617e1a5 100644 --- a/src/material/tabs/tab-group.ts +++ b/src/material/tabs/tab-group.ts @@ -28,7 +28,7 @@ import { AfterViewInit, NgZone, } from '@angular/core'; -import {MAT_TAB_GROUP, MatTab} from './tab'; +import {MatTab} from './tab'; import {MatTabHeader} from './tab-header'; import {ThemePalette, MatRipple, _animationsDisabled} from '../core'; import {merge, Subscription} from 'rxjs'; @@ -39,6 +39,7 @@ import {MatTabBody} from './tab-body'; import {CdkPortalOutlet} from '@angular/cdk/portal'; import {MatTabLabelWrapper} from './tab-label-wrapper'; import {Platform} from '@angular/cdk/platform'; +import {MAT_TAB_GROUP} from './tab-group-token'; /** @docs-private */ export interface MatTabGroupBaseHeader { diff --git a/src/material/tabs/tab-label.ts b/src/material/tabs/tab-label.ts index ab9f8c5c1cc0..c8bb104fad20 100644 --- a/src/material/tabs/tab-label.ts +++ b/src/material/tabs/tab-label.ts @@ -8,7 +8,7 @@ import {Directive, InjectionToken, inject} from '@angular/core'; import {CdkPortal} from '@angular/cdk/portal'; -import {type MatTab} from './tab'; +import {MAT_TAB} from './tab-token'; /** * Injection token that can be used to reference instances of `MatTabLabel`. It serves as @@ -17,12 +17,6 @@ import {type MatTab} from './tab'; */ export const MAT_TAB_LABEL = new InjectionToken('MatTabLabel'); -/** - * Used to provide a tab label to a tab without causing a circular dependency. - * @docs-private - */ -export const MAT_TAB = new InjectionToken('MAT_TAB'); - /** Used to flag tab labels for use with the portal directive */ @Directive({ selector: '[mat-tab-label], [matTabLabel]', diff --git a/src/material/tabs/tab-token.ts b/src/material/tabs/tab-token.ts new file mode 100644 index 000000000000..d7a91a5123a2 --- /dev/null +++ b/src/material/tabs/tab-token.ts @@ -0,0 +1,13 @@ +import {InjectionToken} from '@angular/core'; + +/** + * Used to provide a tab without causing a circular dependency. + * @docs-private + */ +export interface MatTabBase {} + +/** + * Used to provide a tab without causing a circular dependency. + * @docs-private + */ +export const MAT_TAB = new InjectionToken('MAT_TAB'); diff --git a/src/material/tabs/tab.ts b/src/material/tabs/tab.ts index 2288dc35509c..24a0f891564d 100644 --- a/src/material/tabs/tab.ts +++ b/src/material/tabs/tab.ts @@ -24,18 +24,13 @@ import { inject, } from '@angular/core'; import {MatTabContent} from './tab-content'; -import {MAT_TAB, MatTabLabel} from './tab-label'; +import {MatTabLabel} from './tab-label'; import {TemplatePortal} from '@angular/cdk/portal'; import {Subject} from 'rxjs'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; import {_StructuralStylesLoader} from '../core'; -import {type MatTabGroup} from './tab-group'; - -/** - * Used to provide a tab group to a tab without causing a circular dependency. - * @docs-private - */ -export const MAT_TAB_GROUP = new InjectionToken('MAT_TAB_GROUP'); +import {MAT_TAB, MatTabBase} from './tab-token'; +import {MAT_TAB_GROUP} from './tab-group-token'; @Component({ selector: 'mat-tab', @@ -57,7 +52,7 @@ export const MAT_TAB_GROUP = new InjectionToken('MAT_TAB_GROUP'); '[attr.id]': 'null', }, }) -export class MatTab implements OnInit, OnChanges, OnDestroy { +export class MatTab implements MatTabBase, OnInit, OnChanges, OnDestroy { private _viewContainerRef = inject(ViewContainerRef); _closestTabGroup = inject(MAT_TAB_GROUP, {optional: true}); @@ -79,11 +74,10 @@ export class MatTab implements OnInit, OnChanges, OnDestroy { * Template provided in the tab content that will be used if present, used to enable lazy-loading */ @ContentChild(MatTabContent, {read: TemplateRef, static: true}) - // The value will be set in `ngAfterViewInit`. private _explicitContent?: TemplateRef; /** Template inside the MatTab view that contains an ``. */ - @ViewChild(TemplateRef, {static: true}) _implicitContent: TemplateRef; + @ViewChild(TemplateRef, {static: true}) _implicitContent?: TemplateRef; /** Plain text label for the tab, used when there is no template label. */ @Input('label') textLabel: string = ''; @@ -155,7 +149,7 @@ export class MatTab implements OnInit, OnChanges, OnDestroy { ngOnInit(): void { this._contentPortal = new TemplatePortal( - this._explicitContent || this._implicitContent, + this._explicitContent || this._implicitContent!, this._viewContainerRef, ); } From fb03e1e1dd03d4ab78cf325fe7dde9dd759e1b52 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 11 Apr 2025 15:48:14 +0200 Subject: [PATCH 11/13] fix(multiple): update material api goldens --- goldens/material/snack-bar/index.api.md | 29 ++++++++++++++---------- goldens/material/stepper/index.api.md | 2 +- goldens/material/tabs/index.api.md | 22 ++++++++++++------ goldens/material/timepicker/index.api.md | 4 ++-- goldens/material/tooltip/index.api.md | 6 ++--- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/goldens/material/snack-bar/index.api.md b/goldens/material/snack-bar/index.api.md index 111d667f63fc..3ec8d9892082 100644 --- a/goldens/material/snack-bar/index.api.md +++ b/goldens/material/snack-bar/index.api.md @@ -32,10 +32,10 @@ import { TemplateRef } from '@angular/core'; import { ViewContainerRef } from '@angular/core'; // @public -export const MAT_SNACK_BAR_DATA: InjectionToken; +export const MAT_SNACK_BAR_DATA: InjectionToken; // @public -export const MAT_SNACK_BAR_DEFAULT_OPTIONS: InjectionToken>; +export const MAT_SNACK_BAR_DEFAULT_OPTIONS: InjectionToken>; // @public @deprecated export function MAT_SNACK_BAR_DEFAULT_OPTIONS_FACTORY(): MatSnackBarConfig; @@ -48,10 +48,10 @@ export class MatSnackBar implements OnDestroy { // (undocumented) ngOnDestroy(): void; open(message: string, action?: string, config?: MatSnackBarConfig): MatSnackBarRef; - get _openedSnackBarRef(): MatSnackBarRef | null; - set _openedSnackBarRef(value: MatSnackBarRef | null); - openFromComponent(component: ComponentType, config?: MatSnackBarConfig): MatSnackBarRef; - openFromTemplate(template: TemplateRef, config?: MatSnackBarConfig): MatSnackBarRef>; + get _openedSnackBarRef(): MatSnackBarRef | null; + set _openedSnackBarRef(value: MatSnackBarRef | null); + openFromComponent(component: ComponentType, config?: MatSnackBarConfig): MatSnackBarRef; + openFromTemplate(template: TemplateRef, config?: MatSnackBarConfig): MatSnackBarRef>; simpleSnackBarComponent: typeof SimpleSnackBar; snackBarContainerComponent: typeof MatSnackBarContainer; // (undocumented) @@ -82,7 +82,7 @@ export const matSnackBarAnimations: { }; // @public -export class MatSnackBarConfig { +export class MatSnackBarConfig { announcementMessage?: string; data?: D | null; direction?: Direction; @@ -174,7 +174,7 @@ export class SimpleSnackBar implements TextOnlySnackBar { constructor(...args: unknown[]); action(): void; // (undocumented) - data: any; + data: TextOnlySnackBarData; get hasAction(): boolean; // (undocumented) snackBarRef: MatSnackBarRef; @@ -189,16 +189,21 @@ export interface TextOnlySnackBar { // (undocumented) action: () => void; // (undocumented) - data: { - message: string; - action: string; - }; + data: TextOnlySnackBarData; // (undocumented) hasAction: boolean; // (undocumented) snackBarRef: MatSnackBarRef; } +// @public +export interface TextOnlySnackBarData { + // (undocumented) + action: string; + // (undocumented) + message: string; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/goldens/material/stepper/index.api.md b/goldens/material/stepper/index.api.md index c4ecf404a356..e61b12fa3f13 100644 --- a/goldens/material/stepper/index.api.md +++ b/goldens/material/stepper/index.api.md @@ -64,7 +64,7 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI export class MatStepContent { constructor(...args: unknown[]); // (undocumented) - _template: TemplateRef; + _template: TemplateRef; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) diff --git a/goldens/material/tabs/index.api.md b/goldens/material/tabs/index.api.md index abe762fdddd8..23dd35c6b1be 100644 --- a/goldens/material/tabs/index.api.md +++ b/goldens/material/tabs/index.api.md @@ -35,13 +35,13 @@ export const _MAT_INK_BAR_POSITIONER: InjectionToken<_MatInkBarPositioner>; export function _MAT_INK_BAR_POSITIONER_FACTORY(): _MatInkBarPositioner; // @public -export const MAT_TAB: InjectionToken; +export const MAT_TAB: InjectionToken; // @public export const MAT_TAB_CONTENT: InjectionToken; // @public -export const MAT_TAB_GROUP: InjectionToken; +export const MAT_TAB_GROUP: InjectionToken; // @public export const MAT_TAB_LABEL: InjectionToken; @@ -143,17 +143,17 @@ export abstract class MatPaginatedTabHeader implements AfterContentChecked, Afte } // @public (undocumented) -export class MatTab implements OnInit, OnChanges, OnDestroy { +export class MatTab implements MatTabBase, OnInit, OnChanges, OnDestroy { constructor(...args: unknown[]); ariaLabel: string; ariaLabelledby: string; bodyClass: string | string[]; // (undocumented) - _closestTabGroup: any; + _closestTabGroup: MatTabGroupBase | null; get content(): TemplatePortal | null; disabled: boolean; id: string | null; - _implicitContent: TemplateRef; + _implicitContent?: TemplateRef; isActive: boolean; labelClass: string | string[]; // (undocumented) @@ -176,6 +176,10 @@ export class MatTab implements OnInit, OnChanges, OnDestroy { static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public +export interface MatTabBase { +} + // @public export class MatTabBody implements OnInit, OnDestroy { constructor(...args: unknown[]); @@ -231,7 +235,7 @@ export class MatTabChangeEvent { export class MatTabContent { constructor(...args: unknown[]); // (undocumented) - template: TemplateRef; + template: TemplateRef; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) @@ -321,6 +325,10 @@ export class MatTabGroup implements AfterViewInit, AfterContentInit, AfterConten static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public +export interface MatTabGroupBase { +} + // @public export interface MatTabGroupBaseHeader { // (undocumented) @@ -368,7 +376,7 @@ export type MatTabHeaderPosition = 'above' | 'below'; // @public export class MatTabLabel extends CdkPortal { // (undocumented) - _closestTab: any; + _closestTab: MatTabBase | null; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) diff --git a/goldens/material/timepicker/index.api.md b/goldens/material/timepicker/index.api.md index f563743b636f..7bdda76766a6 100644 --- a/goldens/material/timepicker/index.api.md +++ b/goldens/material/timepicker/index.api.md @@ -94,14 +94,14 @@ export class MatTimepickerInput implements ControlValueAccessor, Validator, O readonly min: InputSignalWithTransform; // (undocumented) ngOnDestroy(): void; - registerOnChange(fn: (value: any) => void): void; + registerOnChange(fn: (value: unknown) => void): void; registerOnTouched(fn: () => void): void; registerOnValidatorChange(fn: () => void): void; setDisabledState(isDisabled: boolean): void; readonly timepicker: InputSignal>; validate(control: AbstractControl): ValidationErrors | null; readonly value: ModelSignal; - writeValue(value: any): void; + writeValue(value: unknown): void; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration, "input[matTimepicker]", ["matTimepickerInput"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "timepicker": { "alias": "matTimepicker"; "required": true; "isSignal": true; }; "min": { "alias": "matTimepickerMin"; "required": false; "isSignal": true; }; "max": { "alias": "matTimepickerMax"; "required": false; "isSignal": true; }; "disabledInput": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>; // (undocumented) diff --git a/goldens/material/tooltip/index.api.md b/goldens/material/tooltip/index.api.md index d81a8dd9a0b6..9426411c62e4 100644 --- a/goldens/material/tooltip/index.api.md +++ b/goldens/material/tooltip/index.api.md @@ -88,10 +88,10 @@ export class MatTooltip implements OnDestroy, AfterViewInit { y: number; }): void; get tooltipClass(): string | string[] | Set | { - [key: string]: any; + [key: string]: unknown; }; set tooltipClass(value: string | string[] | Set | { - [key: string]: any; + [key: string]: unknown; }); // (undocumented) _tooltipInstance: TooltipComponent | null; @@ -160,7 +160,7 @@ export class TooltipComponent implements OnDestroy { show(delay: number): void; _tooltip: ElementRef; tooltipClass: string | string[] | Set | { - [key: string]: any; + [key: string]: unknown; }; _triggerElement: HTMLElement; // (undocumented) From 1c6836de830fb6b2e7f9de9d92eeeef4bc89a64b Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 11 Apr 2025 15:59:12 +0200 Subject: [PATCH 12/13] build(material/tabs): fix BUILD after adding new files --- src/material/tabs/BUILD.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/material/tabs/BUILD.bazel b/src/material/tabs/BUILD.bazel index 6d860b1c1b58..f44ec21a9833 100644 --- a/src/material/tabs/BUILD.bazel +++ b/src/material/tabs/BUILD.bazel @@ -116,10 +116,12 @@ ng_project( "tab-config.ts", "tab-content.ts", "tab-group.ts", + "tab-group-token.ts", "tab-header.ts", "tab-label.ts", "tab-label-wrapper.ts", "tab-nav-bar/tab-nav-bar.ts", + "tab-token.ts", "tabs-animations.ts", ], assets = [ From 0a1fbca07801af296871dcf4cc64d43ba7da43d1 Mon Sep 17 00:00:00 2001 From: Dominik Rabij Date: Fri, 18 Apr 2025 12:40:27 +0200 Subject: [PATCH 13/13] fix(material/tabs): fix lint errors --- src/material/tabs/tab-group-token.ts | 8 ++++++++ src/material/tabs/tab-token.ts | 8 ++++++++ src/material/tabs/tab.ts | 1 - 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/material/tabs/tab-group-token.ts b/src/material/tabs/tab-group-token.ts index b55cbeea6f6e..689eb13e03dc 100644 --- a/src/material/tabs/tab-group-token.ts +++ b/src/material/tabs/tab-group-token.ts @@ -1,3 +1,11 @@ +/** + * @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 {InjectionToken} from '@angular/core'; /** diff --git a/src/material/tabs/tab-token.ts b/src/material/tabs/tab-token.ts index d7a91a5123a2..657d9e003cd4 100644 --- a/src/material/tabs/tab-token.ts +++ b/src/material/tabs/tab-token.ts @@ -1,3 +1,11 @@ +/** + * @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 {InjectionToken} from '@angular/core'; /** diff --git a/src/material/tabs/tab.ts b/src/material/tabs/tab.ts index 24a0f891564d..93d48d09b1f2 100644 --- a/src/material/tabs/tab.ts +++ b/src/material/tabs/tab.ts @@ -10,7 +10,6 @@ import { ChangeDetectionStrategy, Component, ContentChild, - InjectionToken, Input, OnChanges, OnDestroy,