diff --git a/src/dev-app/aria-tree/tree-demo.ts b/src/dev-app/aria-tree/tree-demo.ts
index 9807633d0653..bdd42762f58c 100644
--- a/src/dev-app/aria-tree/tree-demo.ts
+++ b/src/dev-app/aria-tree/tree-demo.ts
@@ -18,6 +18,7 @@ import {
TreeNavExample,
TreeSingleSelectExample,
TreeSingleSelectFollowFocusExample,
+ TreeNavRtlExample,
} from '@angular/components-examples/aria/tree';
@Component({
@@ -31,6 +32,7 @@ import {
TreeMultiSelectExample,
TreeMultiSelectFollowFocusExample,
TreeNavExample,
+ TreeNavRtlExample,
TreeSingleSelectExample,
TreeSingleSelectFollowFocusExample,
],
diff --git a/src/dev-app/tsconfig.json b/src/dev-app/tsconfig.json
index b5603f11cb1b..b697d2e671cf 100644
--- a/src/dev-app/tsconfig.json
+++ b/src/dev-app/tsconfig.json
@@ -4,7 +4,6 @@
"compilerOptions": {
// Needed for Moment.js since it doesn't have a default export.
"rootDir": "..",
- "baseUrl": ".",
"paths": {
"@angular/aria": ["../aria"],
"@angular/aria/*": ["../aria/*"],
diff --git a/src/e2e-app/tsconfig.json b/src/e2e-app/tsconfig.json
index 4f0a5cb5f393..30db2302267f 100644
--- a/src/e2e-app/tsconfig.json
+++ b/src/e2e-app/tsconfig.json
@@ -3,7 +3,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
- "baseUrl": ".",
"paths": {
"@angular/cdk/*": ["../cdk/*"],
"@angular/material/*": ["../material/*"],
diff --git a/src/google-maps/map-marker-clusterer/map-marker-clusterer-types.ts b/src/google-maps/map-marker-clusterer/map-marker-clusterer-types.ts
index 0401167bb7be..23d5813ad8f3 100644
--- a/src/google-maps/map-marker-clusterer/map-marker-clusterer-types.ts
+++ b/src/google-maps/map-marker-clusterer/map-marker-clusterer-types.ts
@@ -22,13 +22,12 @@ export interface ClusterOptions {
export interface Cluster {
marker?: Marker;
- readonly markers?: Marker[];
+ markers?: Marker[];
bounds?: google.maps.LatLngBounds;
position: google.maps.LatLng;
count: number;
push(marker: Marker): void;
delete(): void;
- new (options: ClusterOptions): Cluster;
}
export declare class MarkerClusterer extends google.maps.OverlayView {
diff --git a/src/google-maps/tsconfig.json b/src/google-maps/tsconfig.json
index 3d34fe24db16..2b8eab64392b 100644
--- a/src/google-maps/tsconfig.json
+++ b/src/google-maps/tsconfig.json
@@ -3,7 +3,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
- "baseUrl": ".",
"paths": {},
"types": ["jasmine", "google.maps"]
},
diff --git a/src/material-date-fns-adapter/tsconfig.json b/src/material-date-fns-adapter/tsconfig.json
index a8acc041c9ca..064ffc3855fb 100644
--- a/src/material-date-fns-adapter/tsconfig.json
+++ b/src/material-date-fns-adapter/tsconfig.json
@@ -3,7 +3,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
- "baseUrl": ".",
"paths": {
"@angular/cdk/*": ["../cdk/*"],
"@angular/material/*": ["../material/*"],
diff --git a/src/material-experimental/column-resize/column-resize.spec.ts b/src/material-experimental/column-resize/column-resize.spec.ts
index e447cf08b38d..6992936669c9 100644
--- a/src/material-experimental/column-resize/column-resize.spec.ts
+++ b/src/material-experimental/column-resize/column-resize.spec.ts
@@ -9,7 +9,7 @@ import {
Injectable,
ViewChild,
} from '@angular/core';
-import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing';
+import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing';
import {MatTableModule} from '@angular/material/table';
import {BehaviorSubject, Observable, ReplaySubject} from 'rxjs';
import {dispatchKeyboardEvent} from '../../cdk/testing/private';
@@ -406,6 +406,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
expect(
component.getOverlayThumbElement(0).classList.contains('mat-column-resize-overlay-thumb'),
@@ -435,6 +436,7 @@ describe('Material Popover Edit', () => {
component.completeResizeWithMouseInProgress(0);
component.endHoverState();
fixture.detectChanges();
+ tick(200);
flush();
expect(component.getOverlayThumbElement(0)).toBeUndefined();
@@ -448,6 +450,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
component.beginColumnResizeWithMouse(1);
const initialThumbPosition = component.getOverlayThumbPosition(1);
@@ -497,6 +500,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
component.beginColumnResizeWithMouse(1);
const initialThumbPosition = component.getOverlayThumbPosition(1);
@@ -532,6 +536,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
component.beginColumnResizeWithMouse(1, 2);
const initialPosition = component.getOverlayThumbPosition(1);
@@ -549,6 +554,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
component.beginColumnResizeWithMouse(1);
const initialThumbPosition = component.getOverlayThumbPosition(1);
@@ -586,6 +592,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
expect(resize).toBe(null);
@@ -607,6 +614,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
component.beginColumnResizeWithMouse(0);
component.updateResizeWithMouseInProgress(5);
@@ -669,6 +677,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
component.resizeColumnWithMouse(1, 5);
fixture.detectChanges();
@@ -694,6 +703,7 @@ describe('Material Popover Edit', () => {
component.triggerHoverState();
fixture.detectChanges();
+ tick(200);
component.resizeColumnWithMouse(1, 5);
fixture.detectChanges();
diff --git a/src/material-experimental/tsconfig.json b/src/material-experimental/tsconfig.json
index cb9426ac26a8..599c86c4a439 100644
--- a/src/material-experimental/tsconfig.json
+++ b/src/material-experimental/tsconfig.json
@@ -3,7 +3,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
- "baseUrl": ".",
"paths": {
"@angular/cdk/*": ["../cdk/*"],
"@angular/material/*": ["../material/*"],
diff --git a/src/material-luxon-adapter/tsconfig.json b/src/material-luxon-adapter/tsconfig.json
index a8acc041c9ca..064ffc3855fb 100644
--- a/src/material-luxon-adapter/tsconfig.json
+++ b/src/material-luxon-adapter/tsconfig.json
@@ -3,7 +3,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
- "baseUrl": ".",
"paths": {
"@angular/cdk/*": ["../cdk/*"],
"@angular/material/*": ["../material/*"],
diff --git a/src/material-moment-adapter/tsconfig.json b/src/material-moment-adapter/tsconfig.json
index a8acc041c9ca..064ffc3855fb 100644
--- a/src/material-moment-adapter/tsconfig.json
+++ b/src/material-moment-adapter/tsconfig.json
@@ -3,7 +3,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
- "baseUrl": ".",
"paths": {
"@angular/cdk/*": ["../cdk/*"],
"@angular/material/*": ["../material/*"],
diff --git a/src/material/badge/badge.scss b/src/material/badge/badge.scss
index ee771a08aa7f..79da874efc0c 100644
--- a/src/material/badge/badge.scss
+++ b/src/material/badge/badge.scss
@@ -10,7 +10,12 @@ $large-size: $default-size + 6;
$fallbacks: m3-badge.get-tokens();
@mixin _badge-size($size) {
- $prefix: if($size == 'medium', '', $size + '-size-');
+ $prefix: '';
+
+ @if ($size != 'medium') {
+ $prefix: $size + '-size-';
+ }
+
$legacy-size-var-name: 'badge-legacy-#{$prefix}container-size';
$size-var-name: 'badge-#{$prefix}container-size';
diff --git a/src/material/badge/badge.ts b/src/material/badge/badge.ts
index f87d7546153e..482d107625a3 100644
--- a/src/material/badge/badge.ts
+++ b/src/material/badge/badge.ts
@@ -21,8 +21,8 @@ import {
OnInit,
Renderer2,
ViewEncapsulation,
- HOST_TAG_NAME,
DOCUMENT,
+ AfterViewInit,
} from '@angular/core';
import {_animationsDisabled, ThemePalette} from '../core';
import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/private';
@@ -72,7 +72,7 @@ export class _MatBadgeStyleLoader {}
'[class.mat-badge-disabled]': 'disabled',
},
})
-export class MatBadge implements OnInit, OnDestroy {
+export class MatBadge implements OnInit, AfterViewInit, OnDestroy {
private _ngZone = inject(NgZone);
private _elementRef = inject
>(ElementRef);
private _ariaDescriber = inject(AriaDescriber);
@@ -162,22 +162,6 @@ export class MatBadge implements OnInit, OnDestroy {
if (nativeElement.nodeType !== nativeElement.ELEMENT_NODE) {
throw Error('matBadge must be attached to an element node.');
}
-
- const tagName = inject(HOST_TAG_NAME);
-
- // Heads-up for developers to avoid putting matBadge on
- // as it is aria-hidden by default docs mention this at:
- // https://material.angular.dev/components/badge/overview#accessibility
- if (
- tagName.toLowerCase() === 'mat-icon' &&
- nativeElement.getAttribute('aria-hidden') === 'true'
- ) {
- console.warn(
- `Detected a matBadge on an "aria-hidden" "". ` +
- `Consider setting aria-hidden="false" in order to surface the information assistive technology.` +
- `\n${nativeElement.outerHTML}`,
- );
- }
}
}
@@ -213,6 +197,26 @@ export class MatBadge implements OnInit, OnDestroy {
this._isInitialized = true;
}
+ ngAfterViewInit() {
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
+ const nativeElement = this._elementRef.nativeElement;
+
+ // Heads-up for developers to avoid putting matBadge on
+ // as it is aria-hidden by default docs mention this at:
+ // https://material.angular.dev/components/badge/overview#accessibility
+ if (
+ nativeElement.tagName.toLowerCase() === 'mat-icon' &&
+ nativeElement.getAttribute('aria-hidden') === 'true'
+ ) {
+ console.warn(
+ `Detected a matBadge on an "aria-hidden" "". ` +
+ `Consider setting aria-hidden="false" in order to surface the information assistive technology.` +
+ `\n${nativeElement.outerHTML}`,
+ );
+ }
+ }
+ }
+
ngOnDestroy() {
// ViewEngine only: when creating a badge through the Renderer, Angular remembers its index.
// We have to destroy it ourselves, otherwise it'll be retained in memory.
diff --git a/src/material/button/_m2-button.scss b/src/material/button/_m2-button.scss
index 5a792eb543b4..f3d19287d4bd 100644
--- a/src/material/button/_m2-button.scss
+++ b/src/material/button/_m2-button.scss
@@ -16,8 +16,12 @@
-2: 28px,
-3: 24px,
), $scale);
- $touch-target-display: if($scale < -1, none, block);
$touch-target-size: 48px;
+ $touch-target-display: block;
+
+ @if ($scale < -1) {
+ $touch-target-display: none;
+ }
@return (
base: (
diff --git a/src/material/button/_m2-fab.scss b/src/material/button/_m2-fab.scss
index 1653cf1f9c88..d1bea7db9823 100644
--- a/src/material/button/_m2-fab.scss
+++ b/src/material/button/_m2-fab.scss
@@ -17,6 +17,11 @@
$disabled-container : m3-utils.color-with-opacity(map.get($system, on-surface), 12%);
$density-scale: theming.clamp-density(map.get($system, density-scale), -3);
$touch-target-size: 48px;
+ $touch-target-display: block;
+
+ @if ($density-scale < -1) {
+ $touch-target-display: none;
+ }
@return (
base: (
@@ -70,8 +75,8 @@
fab-extended-label-text-weight: map.get($system, label-small-weight)
),
density: (
- fab-small-touch-target-display: if($density-scale < -1, none, block),
- fab-touch-target-display: if($density-scale < -1, none, block),
+ fab-small-touch-target-display: $touch-target-display,
+ fab-touch-target-display: $touch-target-display,
),
);
}
diff --git a/src/material/button/_m2-icon-button.scss b/src/material/button/_m2-icon-button.scss
index 22d6e3582846..f6a420e052d6 100644
--- a/src/material/button/_m2-icon-button.scss
+++ b/src/material/button/_m2-icon-button.scss
@@ -6,6 +6,11 @@
@function get-tokens($theme) {
$system: m2-utils.get-system($theme);
$density-scale: theming.clamp-density(map.get($system, density-scale), -3);
+ $touch-target-display: block;
+
+ @if ($density-scale < -1) {
+ $touch-target-display: none;
+ }
@return (
base: (
@@ -27,7 +32,7 @@
),
typography: (),
density: (
- icon-button-touch-target-display: if($density-scale < -1, none, block),
+ icon-button-touch-target-display: $touch-target-display,
icon-button-state-layer-size: map.get((
0: 48px,
-1: 44px,
diff --git a/src/material/checkbox/_m2-checkbox.scss b/src/material/checkbox/_m2-checkbox.scss
index 18dd374d6f0d..d36da29f792e 100644
--- a/src/material/checkbox/_m2-checkbox.scss
+++ b/src/material/checkbox/_m2-checkbox.scss
@@ -6,6 +6,11 @@
@function get-tokens($theme) {
$system: m2-utils.get-system($theme);
$density-scale: theming.clamp-density(map.get($system, density-scale), -3);
+ $touch-target-display: block;
+
+ @if ($density-scale < -1) {
+ $touch-target-display: none;
+ }
@return (
base: (
@@ -28,7 +33,7 @@
checkbox-label-text-weight: map.get($system, body-medium-weight)
),
density: (
- checkbox-touch-target-display: if($density-scale < -1, none, block),
+ checkbox-touch-target-display: $touch-target-display,
checkbox-state-layer-size: map.get((
0: 40px,
-1: 36px,
diff --git a/src/material/chips/chip-row.ts b/src/material/chips/chip-row.ts
index c3d9453aad81..0b545be2a364 100644
--- a/src/material/chips/chip-row.ts
+++ b/src/material/chips/chip-row.ts
@@ -14,10 +14,13 @@ import {
ContentChild,
EventEmitter,
Input,
+ OnDestroy,
Output,
+ Renderer2,
ViewChild,
ViewEncapsulation,
afterNextRender,
+ inject,
} from '@angular/core';
import {takeUntil} from 'rxjs/operators';
import {MatChip, MatChipEvent} from './chip';
@@ -72,8 +75,10 @@ export interface MatChipEditedEvent extends MatChipEvent {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MatChipAction, MatChipEditInput],
})
-export class MatChipRow extends MatChip implements AfterViewInit {
+export class MatChipRow extends MatChip implements AfterViewInit, OnDestroy {
protected override basicChipAttrName = 'mat-basic-chip-row';
+ private _renderer = inject(Renderer2);
+ private _cleanupMousedown: (() => void) | undefined;
/**
* The editing action has to be triggered in a timeout. While we're waiting on it, a blur
@@ -123,12 +128,16 @@ export class MatChipRow extends MatChip implements AfterViewInit {
super.ngAfterViewInit();
// Sets _alreadyFocused (ahead of click) when chip already has focus.
- this._ngZone.runOutsideAngular(() => {
- this._elementRef.nativeElement.addEventListener(
- 'mousedown',
- () => (this._alreadyFocused = this._hasFocus()),
- );
- });
+ this._cleanupMousedown = this._ngZone.runOutsideAngular(() =>
+ this._renderer.listen(this._elementRef.nativeElement, 'mousedown', () => {
+ this._alreadyFocused = this._hasFocus();
+ }),
+ );
+ }
+
+ override ngOnDestroy(): void {
+ super.ngOnDestroy();
+ this._cleanupMousedown?.();
}
protected _hasLeadingActionIcon() {
diff --git a/src/material/core/m2/_theming.scss b/src/material/core/m2/_theming.scss
index 96d234c8e05f..0154ee1d29a1 100644
--- a/src/material/core/m2/_theming.scss
+++ b/src/material/core/m2/_theming.scss
@@ -84,7 +84,13 @@
// We cast the $hue to a string, because some hues starting with a number, like `700-contrast`,
// might be inferred as numbers by Sass. Casting them to string fixes the map lookup.
- $color: if(map.has-key($palette, $hue), map.get($palette, $hue), map.get($palette, $hue + ''));
+ $color: null;
+
+ @if (map.has-key($palette, $hue)) {
+ $color: map.get($palette, $hue);
+ } @else {
+ $color: map.get($palette, $hue + '');
+ }
@if ($opacity == null) {
@return $color;
@@ -121,12 +127,15 @@
warn: $warn,
);
- $sys-color: if(
- $is-dark,
- m2.md-sys-color-values-dark($palettes),
- m2.md-sys-color-values-light($palettes));
$sys-state: m2.md-sys-state-values();
$sys-typography: m2.md-sys-typescale-values($typography);
+ $sys-color: null;
+
+ @if ($is-dark) {
+ $sys-color: m2.md-sys-color-values-dark($palettes);
+ } @else {
+ $sys-color: m2.md-sys-color-values-light($palettes);
+ }
$system: (density-scale: $density-scale);
@each $map in ($sys-color, $sys-state, $sys-typography) {
@@ -139,16 +148,22 @@
// Creates a color configuration from the specified
// primary, accent and warn palettes.
@function _mat-create-color-config($primary, $accent, $warn: null, $is-dark) {
- $warn: if($warn != null, $warn, define-palette(palette.$red-palette));
- $foreground:
- if($is-dark, palette.$dark-theme-foreground-palette, palette.$light-theme-foreground-palette);
- $background:
- if($is-dark, palette.$dark-theme-background-palette, palette.$light-theme-background-palette);
+ $foreground: null;
+ $background: null;
+ $warn-palette: $warn or define-palette(palette.$red-palette);
+
+ @if ($is-dark) {
+ $foreground: palette.$dark-theme-foreground-palette;
+ $background: palette.$dark-theme-background-palette;
+ } @else {
+ $foreground: palette.$light-theme-foreground-palette;
+ $background: palette.$light-theme-background-palette;
+ }
@return (
primary: $primary,
accent: $accent,
- warn: $warn,
+ warn: $warn-palette,
is-dark: $is-dark,
foreground: $foreground,
background: $background,
diff --git a/src/material/core/m2/_typography-utils.scss b/src/material/core/m2/_typography-utils.scss
index 163b8aa3b98a..0f5ada1b2149 100644
--- a/src/material/core/m2/_typography-utils.scss
+++ b/src/material/core/m2/_typography-utils.scss
@@ -73,5 +73,9 @@
}
// Guard against unquoting non-string values, because it's deprecated.
- @return if(meta.type-of($font-family) == string, string.unquote($font-family), $font-family);
+ @if (meta.type-of($font-family) == string) {
+ $font-family: string.unquote($font-family);
+ }
+
+ @return $font-family;
}
diff --git a/src/material/core/style/_elevation.scss b/src/material/core/style/_elevation.scss
index 1702cfb095f8..5a3d8c98b0ea 100644
--- a/src/material/core/style/_elevation.scss
+++ b/src/material/core/style/_elevation.scss
@@ -159,8 +159,12 @@ $prefix: 'mat-elevation-z';
$umbra-z-value: map.get($_umbra-map, $zValue);
$penumbra-z-value: map.get($_penumbra-map, $zValue);
$ambient-z-value: map.get($_ambient-map, $zValue);
+ $color-opacity: 1;
+
+ @if ($opacity != null) {
+ $color-opacity: $opacity;
+ }
- $color-opacity: if($opacity != null, $opacity, 1);
$umbra-color: _compute-color-opacity($shadow-color, $_umbra-opacity * $color-opacity);
$penumbra-color: _compute-color-opacity($shadow-color, $_penumbra-opacity * $color-opacity);
$ambient-color: _compute-color-opacity($shadow-color, $_ambient-opacity * $color-opacity);
diff --git a/src/material/core/style/_validation.scss b/src/material/core/style/_validation.scss
index 41b9eafe4e1f..40502504912f 100644
--- a/src/material/core/style/_validation.scss
+++ b/src/material/core/style/_validation.scss
@@ -11,7 +11,10 @@
@if ($result == list and list.index($types, map) and list.length($obj) == 0) {
@return null;
}
- @return if(list.index($types, $result), null, $result);
+ @if (list.index($types, $result)) {
+ @return null;
+ }
+ @return $result;
}
/// Validates that a list contains only values from the allowed list of values.
@@ -25,7 +28,10 @@
$invalid: list.append($invalid, $element);
}
}
- @return if(list.length($invalid) > 0, $invalid, null);
+ @if (list.length($invalid) > 0) {
+ @return $invalid;
+ }
+ @return null;
}
/// Validates that a list contains all values from the required list of values.
@@ -39,5 +45,8 @@
$invalid: list.append($invalid, $element);
}
}
- @return if(list.length($invalid) > 0, $invalid, null);
+ @if (list.length($invalid) > 0) {
+ @return $invalid;
+ }
+ @return null;
}
diff --git a/src/material/core/theming/_color-api-backwards-compatibility.scss b/src/material/core/theming/_color-api-backwards-compatibility.scss
index 0b90e7adcfb5..1e1b75af91f2 100644
--- a/src/material/core/theming/_color-api-backwards-compatibility.scss
+++ b/src/material/core/theming/_color-api-backwards-compatibility.scss
@@ -24,7 +24,11 @@
$_overrides-only: true;
@mixin _color-variant-styles($theme, $color-variant) {
- $secondary-when-primary: if($color-variant == primary, secondary, $color-variant);
+ $secondary-when-primary: $color-variant;
+
+ @if ($color-variant == primary) {
+ $secondary-when-primary: secondary;
+ }
& {
@if ($color-variant != primary) {
diff --git a/src/material/core/theming/_inspection.scss b/src/material/core/theming/_inspection.scss
index 107d7f580038..ec096c59c8fb 100644
--- a/src/material/core/theming/_inspection.scss
+++ b/src/material/core/theming/_inspection.scss
@@ -31,7 +31,10 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac
@function _validate-theme-object($theme) {
$err: validation.validate-type($theme, 'map') or
map.get($theme, $internals, theme-version) == null;
- @return if($err, true, null);
+ @if ($err) {
+ @return true;
+ }
+ @return null;
}
/// Gets the version number of a theme object. A theme that is not a valid versioned theme object is
@@ -40,7 +43,12 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac
/// @return {Number} The version number of the theme (0 if unknown).
@function get-theme-version($theme) {
$err: _validate-theme-object($theme);
- @return if($err, 0, map.get($theme, $internals, theme-version) or 0);
+
+ @if ($err) {
+ @return 0;
+ }
+
+ @return map.get($theme, $internals, theme-version) or 0;
}
/// Gets the type of theme represented by a theme object (light or dark).
@@ -99,10 +107,10 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac
@if $args-count < 2 or $args-count > 3 {
@error 'Expected either 2 or 3 arguments when working with an M3 theme. Got: #{$args-count}';
}
- @return if($args-count == 2,
- _get-theme-role-color($theme, $args...),
- _get-theme-palette-color($theme, $args...)
- );
+ @if ($args-count == 2) {
+ @return _get-theme-role-color($theme, $args...);
+ }
+ @return _get-theme-palette-color($theme, $args...);
}
@error 'Unrecognized theme version: #{$version}';
diff --git a/src/material/core/theming/_m2-inspection.scss b/src/material/core/theming/_m2-inspection.scss
index 60e8a30fd655..f6ef37ef6f88 100644
--- a/src/material/core/theming/_m2-inspection.scss
+++ b/src/material/core/theming/_m2-inspection.scss
@@ -49,7 +49,10 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac
}
$internal: map.get($theme, $_internals, m2-config);
$theme: map.remove($theme, $_internals);
- @return if(_is-error-theme($theme), $internal, $theme);
+ @if (_is-error-theme($theme)) {
+ @return $internal;
+ }
+ @return $theme;
}
/// Checks whether the given theme contains error values.
@@ -99,7 +102,10 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac
@if theme-has($colors, color) {
$colors: m2-theming.get-color-config($colors);
}
- @return if(map.get($colors, is-dark), dark, light);
+ @if (map.get($colors, is-dark)) {
+ @return dark;
+ }
+ @return light;
}
/// Gets a color from a theme object. This function can take between 2 and 4 arguments. The first
diff --git a/src/material/core/theming/_theming.scss b/src/material/core/theming/_theming.scss
index 98a1f9cc217b..0b9ffa7eb2a7 100644
--- a/src/material/core/theming/_theming.scss
+++ b/src/material/core/theming/_theming.scss
@@ -45,7 +45,10 @@ $private-internal-name: _mat-theming-internals-do-not-access;
$result: map.set($result, $key, $value);
}
}
- @return if($result == (), null, $result);
+ @if ($result == ()) {
+ @return null;
+ }
+ @return $result;
}
// Checks whether the given value resolves to a theme object. Theme objects are always
diff --git a/src/material/core/tokens/_m3-tokens.scss b/src/material/core/tokens/_m3-tokens.scss
index 2b1334a9b7d1..e8a35fe94b78 100644
--- a/src/material/core/tokens/_m3-tokens.scss
+++ b/src/material/core/tokens/_m3-tokens.scss
@@ -39,9 +39,13 @@
}
@function get-sys-color($type, $palettes, $prefix) {
- $sys-color: if($type == dark,
- m3.md-sys-color-values-dark($palettes),
- m3.md-sys-color-values-light($palettes));
+ $sys-color: null;
+
+ @if ($type == dark) {
+ $sys-color: m3.md-sys-color-values-dark($palettes);
+ } @else {
+ $sys-color: m3.md-sys-color-values-light($palettes);
+ }
@if (sass-utils.$use-system-color-variables) {
$var-values: ();
diff --git a/src/material/core/tokens/_system.scss b/src/material/core/tokens/_system.scss
index 3d4e72af14f5..7cc7cd463dc4 100644
--- a/src/material/core/tokens/_system.scss
+++ b/src/material/core/tokens/_system.scss
@@ -347,10 +347,14 @@
$color: map.get($config, color);
@if (m2-inspection.theme-has($theme-config, color)) {
- $system-colors: if(map.get($color, is-dark),
- m2.md-sys-color-values-dark($color),
- m2.md-sys-color-values-light($color),
- );
+ $system-colors: null;
+
+ @if (map.get($color, is-dark)) {
+ $system-colors: m2.md-sys-color-values-dark($color);
+ } @else {
+ $system-colors: m2.md-sys-color-values-light($color);
+ }
+
@include _define-m2-system-vars($system-colors, $overrides);
$shadow: map.get($theme-config, _mat-system, shadow);
diff --git a/src/material/core/tokens/_token-utils.scss b/src/material/core/tokens/_token-utils.scss
index fdc07754e4d9..56be21d0df12 100644
--- a/src/material/core/tokens/_token-utils.scss
+++ b/src/material/core/tokens/_token-utils.scss
@@ -90,8 +90,13 @@
@each $config in $namespace-configs {
$namespace: map.get($config, namespace);
- $prefix: if(map.has-key($config, prefix), map.get($config, prefix), '');
$tokens: map.get(map.get($config, tokens), all);
+ $prefix: '';
+
+ @if (map.has-key($config, prefix)) {
+ $prefix: map.get($config, prefix);
+ }
+
@each $name, $value in $tokens {
$prefixed-name: $prefix + $name;
$all-names: list.append($all-names, $prefixed-name, $separator: comma);
diff --git a/src/material/core/typography/_versioning.scss b/src/material/core/typography/_versioning.scss
index 1cd1011d84c4..0d4c09dd4e53 100644
--- a/src/material/core/typography/_versioning.scss
+++ b/src/material/core/typography/_versioning.scss
@@ -82,9 +82,8 @@
body-2: map.get($config, body-1),
button: map.get($config, button),
caption: map.get($config, caption),
- overline: if(map.get($config, overline), map.get($config, overline),
- m2-typography.define-typography-level(12px, 32px, 500)
- )
+ overline:
+ map.get($config, overline) or m2-typography.define-typography-level(12px, 32px, 500),
);
}
@return $config;
diff --git a/src/material/form-field/_m2-form-field.scss b/src/material/form-field/_m2-form-field.scss
index 1a3731bd8aee..0c946f1d0e17 100644
--- a/src/material/form-field/_m2-form-field.scss
+++ b/src/material/form-field/_m2-form-field.scss
@@ -18,6 +18,21 @@
$is-dark: inspection.get-theme-type($theme) == dark;
}
+ // Note the spelling of the `GrayText` here which is a system color. See:
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/system-color
+ $select-disabled-option-text-color: GrayText;
+ $select-option-text-color: inherit;
+
+ @if ($is-dark) {
+ // On dark themes we set the native `select` color to some shade of white,
+ // however the color propagates to all of the `option` elements, which are
+ // always on a white background inside the dropdown, causing them to blend in.
+ // Since we can't change background of the dropdown, we need to explicitly
+ // reset the color of the options to something dark.
+ $select-option-text-color: m2-palette.$dark-primary-text;
+ $select-disabled-option-text-color: m2-palette.$dark-disabled-text;
+ }
+
@return (
base: (
form-field-filled-active-indicator-height: 1px,
@@ -32,17 +47,8 @@
form-field-disabled-input-text-placeholder-color: $disabled,
form-field-state-layer-color: map.get($system, on-surface),
form-field-error-text-color: map.get($system, error),
-
- // On dark themes we set the native `select` color to some shade of white,
- // however the color propagates to all of the `option` elements, which are
- // always on a white background inside the dropdown, causing them to blend in.
- // Since we can't change background of the dropdown, we need to explicitly
- // reset the color of the options to something dark.
- form-field-select-option-text-color: if($is-dark, m2-palette.$dark-primary-text, inherit),
- // Note the spelling of the `GrayText` here which is a system color. See:
- // https://developer.mozilla.org/en-US/docs/Web/CSS/system-color
- form-field-select-disabled-option-text-color:
- if($is-dark, m2-palette.$dark-disabled-text, GrayText),
+ form-field-select-option-text-color: $select-option-text-color,
+ form-field-select-disabled-option-text-color: $select-disabled-option-text-color,
// These tokens are necessary for M3. MDC has them built in already, but:
// 1. They are too specific, breaking a lot of internal clients.
@@ -199,14 +205,22 @@
$filled-with-label-padding-top: 24px - $vertical-deduction;
$filled-with-label-padding-bottom: 8px - $vertical-deduction;
$vertical-padding: 16px - $vertical-deduction;
+ $filled-label-display: block;
+ $filled-with-label-container-padding-top: $filled-with-label-padding-top;
+ $filled-with-label-container-padding-bottom: $filled-with-label-padding-bottom;
+
+ @if ($hide-label) {
+ $filled-label-display: none;
+ $filled-with-label-container-padding-top: $vertical-padding;
+ $filled-with-label-container-padding-bottom: $vertical-padding;
+ }
@return (
form-field-container-height: $height,
- form-field-filled-label-display: if($hide-label, none, block),
+ form-field-filled-label-display: $filled-label-display,
form-field-container-vertical-padding: $vertical-padding,
- form-field-filled-with-label-container-padding-top:
- if($hide-label, $vertical-padding, $filled-with-label-padding-top),
+ form-field-filled-with-label-container-padding-top: $filled-with-label-container-padding-top,
form-field-filled-with-label-container-padding-bottom:
- if($hide-label, $vertical-padding, $filled-with-label-padding-bottom),
+ $filled-with-label-container-padding-bottom,
);
}
diff --git a/src/material/form-field/_mdc-text-field-structure-overrides.scss b/src/material/form-field/_mdc-text-field-structure-overrides.scss
index e3df7f0db167..9bcc1572612f 100644
--- a/src/material/form-field/_mdc-text-field-structure-overrides.scss
+++ b/src/material/form-field/_mdc-text-field-structure-overrides.scss
@@ -3,14 +3,7 @@
@use '../core/style/vendor-prefixes';
$fallbacks: m3-form-field.get-tokens();
-
-// TODO(b/263527625): should be removed when this is addressed on the MDC side.
-// MDC sets a will-change on this element, because of the animation. This can cause
-// scrolling performance degradation on pages with a lot of form fields so we reset it.
-// The animation is on a `transform` which is hardware-accelerated already.
-// This flag is used to re-add the `will-change` internally since removing it causes a
-// lot of screenshot diffs.
-$_enable-form-field-will-change-reset: true;
+$_is-external-build: true;
// Mixin that can be included to override the default MDC text-field
// styles to fit our needs. See individual comments for context on why
@@ -45,7 +38,13 @@ $_enable-form-field-will-change-reset: true;
// clicking the label to focus the input.
pointer-events: all;
- @if ($_enable-form-field-will-change-reset) {
+ // TODO(b/263527625): should be removed when this is addressed on the MDC side.
+ // MDC sets a will-change on this element, because of the animation. This can cause
+ // scrolling performance degradation on pages with a lot of form fields so we reset it.
+ // The animation is on a `transform` which is hardware-accelerated already.
+ // This flag is used to re-add the `will-change` internally since removing it causes a
+ // lot of screenshot diffs.
+ @if ($_is-external-build) {
will-change: auto;
}
}
@@ -81,7 +80,8 @@ $_enable-form-field-will-change-reset: true;
height: auto;
flex: auto;
- @if ($_enable-form-field-will-change-reset) {
+ // TODO(b/263527625): should be removed when this is addressed on the MDC side.
+ @if ($_is-external-build) {
will-change: auto;
}
}
diff --git a/src/material/paginator/_m2-paginator.scss b/src/material/paginator/_m2-paginator.scss
index 8af8916314d9..3674e11dc2ec 100644
--- a/src/material/paginator/_m2-paginator.scss
+++ b/src/material/paginator/_m2-paginator.scss
@@ -7,6 +7,16 @@
@function get-tokens($theme) {
$system: m2-utils.get-system($theme);
$density-scale: theming.clamp-density(map.get($system, density-scale), -5);
+ $touch-target-display: block;
+ $form-field-density-scale: $density-scale;
+
+ @if ($form-field-density-scale > -4) {
+ $form-field-density-scale: -4;
+ }
+
+ @if ($density-scale < -2) {
+ $touch-target-display: none;
+ }
$form-field-height: map.get((
0: 56px,
@@ -15,7 +25,7 @@
-3: 44px,
-4: 40px,
-5: 36px,
- ), if($density-scale > -4, -4, $density-scale));
+ ), $form-field-density-scale);
@return (
base: (
@@ -53,7 +63,7 @@
// padding that is provided by the Material Design specification.
paginator-form-field-container-vertical-padding:
16px - math.div(56px - $form-field-height, 2),
- paginator-touch-target-display: if($density-scale < -2, none, block),
+ paginator-touch-target-display: $touch-target-display,
),
);
}
diff --git a/src/material/progress-bar/_m2-progress-bar.scss b/src/material/progress-bar/_m2-progress-bar.scss
index c14c9fdbc1b8..7308b9e6f036 100644
--- a/src/material/progress-bar/_m2-progress-bar.scss
+++ b/src/material/progress-bar/_m2-progress-bar.scss
@@ -23,13 +23,14 @@
@function private-get-color-palette-color-tokens($theme, $color-variant) {
$system: m2-utils.get-system($theme);
$system: m3-utils.replace-colors-with-variant($system, primary, $color-variant);
+ $track-color: map.get($system, primary);
+
+ @if (meta.type-of($track-color) == color) {
+ $track-color: color.adjust($track-color, $alpha: -0.75);
+ }
@return (
progress-bar-active-indicator-color: map.get($system, primary),
- progress-bar-track-color: if(
- meta.type-of(map.get($system, primary)) == color,
- color.adjust(map.get($system, primary), $alpha: -0.75),
- map.get($system, primary)
- )
+ progress-bar-track-color: $track-color,
);
}
diff --git a/src/material/radio/_m2-radio.scss b/src/material/radio/_m2-radio.scss
index 9e4ba08b2197..4f60a4643bcd 100644
--- a/src/material/radio/_m2-radio.scss
+++ b/src/material/radio/_m2-radio.scss
@@ -6,6 +6,11 @@
@function get-tokens($theme) {
$system: m2-utils.get-system($theme);
$density-scale: theming.clamp-density(map.get($system, density-scale), -3);
+ $touch-target-display: block;
+
+ @if ($density-scale < -1) {
+ $touch-target-display: none;
+ }
@return (
base: (
@@ -32,7 +37,7 @@
-2: 32px,
-3: 28px,
), $density-scale),
- radio-touch-target-display: if($density-scale < -1, none, block)
+ radio-touch-target-display: $touch-target-display,
),
);
}
diff --git a/src/material/radio/radio.scss b/src/material/radio/radio.scss
index 2b294bb1cd81..e5440a137d64 100644
--- a/src/material/radio/radio.scss
+++ b/src/material/radio/radio.scss
@@ -4,6 +4,7 @@
@use './radio-common';
$fallbacks: m3-radio.get-tokens();
+$_is-external-build: true;
.mat-mdc-radio-button {
-webkit-tap-highlight-color: transparent;
@@ -15,6 +16,14 @@ $fallbacks: m3-radio.get-tokens();
// user of this. Therefore we add the pointer cursor on top of MDC's styles.
label {
cursor: pointer;
+
+ // TODO(crisbeto): disable internally due to a large amount of breakages.
+ // Prevent the label from taking up space when it's empty.
+ @if ($_is-external-build) {
+ &:empty {
+ display: none;
+ }
+ }
}
.mdc-radio__background::before {
diff --git a/src/material/select/select.html b/src/material/select/select.html
index 6d89042dbeb2..d31f2c834ecd 100644
--- a/src/material/select/select.html
+++ b/src/material/select/select.html
@@ -31,7 +31,6 @@
+ (overlayKeydown)="_handleOverlayKeydown($event)">
+
diff --git a/src/material/select/select.ts b/src/material/select/select.ts
index 222ec39293b6..6292f30c6269 100644
--- a/src/material/select/select.ts
+++ b/src/material/select/select.ts
@@ -94,7 +94,6 @@ import {
getMatSelectNonArrayValueError,
getMatSelectNonFunctionValueError,
} from './select-errors';
-import {NgClass} from '@angular/common';
/** Injection token that determines the scroll handling while a select is open. */
export const MAT_SELECT_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>(
@@ -189,7 +188,7 @@ export class MatSelectChange {
{provide: MatFormFieldControl, useExisting: MatSelect},
{provide: MAT_OPTION_PARENT_COMPONENT, useExisting: MatSelect},
],
- imports: [CdkOverlayOrigin, CdkConnectedOverlay, NgClass],
+ imports: [CdkOverlayOrigin, CdkConnectedOverlay],
})
export class MatSelect
implements
@@ -711,6 +710,10 @@ export class MatSelect
if (changes['typeaheadDebounceInterval'] && this._keyManager) {
this._keyManager.withTypeAhead(this.typeaheadDebounceInterval);
}
+
+ if (changes['panelClass'] && this.panelClass instanceof Set) {
+ this.panelClass = Array.from(this.panelClass);
+ }
}
ngOnDestroy() {
@@ -1085,11 +1088,6 @@ export class MatSelect
}
}
- /** Returns the theme to be used on the panel. */
- _getPanelTheme(): string {
- return this._parentFormField ? `mat-${this._parentFormField.color}` : '';
- }
-
/** Whether the select has a value. */
get empty(): boolean {
return !this._selectionModel || this._selectionModel.isEmpty();
diff --git a/src/material/sidenav/_m2-sidenav.scss b/src/material/sidenav/_m2-sidenav.scss
index b4efaccfa671..8c5f4a50819d 100644
--- a/src/material/sidenav/_m2-sidenav.scss
+++ b/src/material/sidenav/_m2-sidenav.scss
@@ -15,7 +15,18 @@
}
$scrim-opacity: 0.6;
$scrim-color: m3-utils.color-with-opacity(map.get($system, surface), 60%);
- $fallback-scrim-color: if($is-dark, rgba(#fff, $scrim-opacity), rgba(#000, $scrim-opacity));
+
+ @if (meta.type-of($scrim-color) == color) {
+ // We use invert() here to have the darken the background color expected to be used.
+ // If the background is light, we use a dark backdrop. If the background is dark, we
+ // use a light backdrop. If the value isn't a color, Sass will throw an error so we
+ // fall back to something generic.
+ $scrim-color: color.invert($scrim-color);
+ } @else if ($is-dark) {
+ $scrim-color: rgba(#fff, $scrim-opacity);
+ } @else {
+ $scrim-color: rgba(#000, $scrim-opacity);
+ }
@return (
base: (
@@ -29,13 +40,7 @@
sidenav-container-text-color: map.get($system, on-surface),
sidenav-content-background-color: map.get($system, background),
sidenav-content-text-color: map.get($system, on-surface),
-
- // We use invert() here to have the darken the background color expected to be used.
- // If the background is light, we use a dark backdrop. If the background is dark, we
- // use a light backdrop. If the value isn't a color, Sass will throw an error so we
- // fall back to something generic.
- sidenav-scrim-color: if(meta.type-of($scrim-color) == color,
- color.invert($scrim-color), $fallback-scrim-color),
+ sidenav-scrim-color: $scrim-color,
),
typography: (),
density: (),
diff --git a/src/material/sidenav/drawer.spec.ts b/src/material/sidenav/drawer.spec.ts
index 326338c2b79e..79ec2bf2d4c7 100644
--- a/src/material/sidenav/drawer.spec.ts
+++ b/src/material/sidenav/drawer.spec.ts
@@ -714,6 +714,22 @@ describe('MatDrawer', () => {
flush();
expect(anchors.map(anchor => anchor.getAttribute('tabindex'))).toEqual([null, null]);
}));
+
+ it('should not trap focus if the drawer container has a backdrop, but is not showing it', fakeAsync(() => {
+ fixture.destroy();
+
+ const hiddenBackdropFixture = TestBed.createComponent(DrawerWithSideAndHiddenBackdrop);
+ hiddenBackdropFixture.detectChanges();
+ tick();
+ hiddenBackdropFixture.detectChanges();
+
+ const anchors = Array.from(
+ hiddenBackdropFixture.nativeElement.querySelectorAll('.cdk-focus-trap-anchor'),
+ );
+
+ expect(anchors.length).toBeGreaterThan(0);
+ expect(anchors.every(anchor => !anchor.hasAttribute('tabindex'))).toBe(true);
+ }));
});
it('should mark the drawer content as scrollable', () => {
@@ -1418,3 +1434,16 @@ class NestedDrawerContainers {
@ViewChild('innerContainer') innerContainer!: MatDrawerContainer;
@ViewChild('innerDrawer') innerDrawer!: MatDrawer;
}
+
+@Component({
+ template: `
+
+
+
+
+ End content
+
+ `,
+ imports: [MatSidenavModule],
+})
+class DrawerWithSideAndHiddenBackdrop {}
diff --git a/src/material/sidenav/drawer.ts b/src/material/sidenav/drawer.ts
index 9fd9b4705cf0..43e394bb5a35 100644
--- a/src/material/sidenav/drawer.ts
+++ b/src/material/sidenav/drawer.ts
@@ -44,7 +44,7 @@ import {
DOCUMENT,
signal,
} from '@angular/core';
-import {fromEvent, merge, Observable, Subject} from 'rxjs';
+import {merge, Observable, Subject} from 'rxjs';
import {debounceTime, filter, map, mapTo, startWith, take, takeUntil} from 'rxjs/operators';
import {_animationsDisabled} from '../core';
@@ -350,27 +350,23 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
* time a key is pressed. Instead we re-enter the zone only if the `ESC` key is pressed
* and we don't have close disabled.
*/
- this._ngZone.runOutsideAngular(() => {
+ this._eventCleanups = this._ngZone.runOutsideAngular(() => {
+ const renderer = this._renderer;
const element = this._elementRef.nativeElement;
- (fromEvent(element, 'keydown') as Observable)
- .pipe(
- filter(event => {
- return event.keyCode === ESCAPE && !this.disableClose && !hasModifierKey(event);
- }),
- takeUntil(this._destroyed),
- )
- .subscribe(event =>
- this._ngZone.run(() => {
- this.close();
- event.stopPropagation();
- event.preventDefault();
- }),
- );
- this._eventCleanups = [
- this._renderer.listen(element, 'transitionrun', this._handleTransitionEvent),
- this._renderer.listen(element, 'transitionend', this._handleTransitionEvent),
- this._renderer.listen(element, 'transitioncancel', this._handleTransitionEvent),
+ return [
+ renderer.listen(element, 'keydown', (event: KeyboardEvent) => {
+ if (event.keyCode === ESCAPE && !this.disableClose && !hasModifierKey(event)) {
+ this._ngZone.run(() => {
+ this.close();
+ event.stopPropagation();
+ event.preventDefault();
+ });
+ }
+ }),
+ renderer.listen(element, 'transitionrun', this._handleTransitionEvent),
+ renderer.listen(element, 'transitionend', this._handleTransitionEvent),
+ renderer.listen(element, 'transitioncancel', this._handleTransitionEvent),
];
});
@@ -573,7 +569,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
this._opened.set(isOpen);
if (this._container?._transitionsEnabled) {
- // Note: it's importatnt to set this as early as possible,
+ // Note: it's important to set this as early as possible,
// otherwise the animation can look glitchy in some cases.
this._setIsAnimating(true);
} else {
@@ -613,7 +609,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
if (this._focusTrap) {
// Trap focus only if the backdrop is enabled. Otherwise, allow end user to interact with the
// sidenav content.
- this._focusTrap.enabled = !!this._container?.hasBackdrop && this.opened;
+ this._focusTrap.enabled = this.opened && !!this._container?._isShowingBackdrop();
}
}
diff --git a/src/material/slide-toggle/_m2-slide-toggle.scss b/src/material/slide-toggle/_m2-slide-toggle.scss
index 2d8aa565c5db..0ff3c251d305 100644
--- a/src/material/slide-toggle/_m2-slide-toggle.scss
+++ b/src/material/slide-toggle/_m2-slide-toggle.scss
@@ -7,6 +7,11 @@
@function get-tokens($theme) {
$system: m2-utils.get-system($theme);
$density-scale: theming.clamp-density(map.get($system, density-scale), -3);
+ $touch-target-display: block;
+
+ @if ($density-scale < -1) {
+ $touch-target-display: none;
+ }
@return (
base: (
@@ -101,7 +106,7 @@
-2: 32px,
-3: 28px,
), $density-scale),
- slide-toggle-touch-target-display: if($density-scale < -1, none, block)
+ slide-toggle-touch-target-display: $touch-target-display
),
);
}
diff --git a/src/material/slide-toggle/slide-toggle.scss b/src/material/slide-toggle/slide-toggle.scss
index dfc10d88bc9f..23d274a3ad08 100644
--- a/src/material/slide-toggle/slide-toggle.scss
+++ b/src/material/slide-toggle/slide-toggle.scss
@@ -33,11 +33,6 @@ $fallbacks: m3-slide-toggle.get-tokens();
}
}
-// Ensure no label element (with a click handler) present to ensure hidden from screen readers.
-label:empty {
- display: none;
-}
-
.mdc-switch__track {
overflow: hidden;
position: relative;
@@ -555,6 +550,11 @@ label:empty {
.mdc-switch--disabled + label {
color: token-utils.slot(slide-toggle-disabled-label-text-color, $fallbacks);
}
+
+ // Ensure no label element (with a click handler) present to ensure hidden from screen readers.
+ label:empty {
+ display: none;
+ }
}
// Element used to provide a larger tap target for users on touch devices.
diff --git a/src/material/table/table-data-source.spec.ts b/src/material/table/table-data-source.spec.ts
index b46c14d2ad08..feaf4a846e2a 100644
--- a/src/material/table/table-data-source.spec.ts
+++ b/src/material/table/table-data-source.spec.ts
@@ -1,11 +1,17 @@
import {MatTableDataSource} from './table-data-source';
-import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {MatSort, MatSortModule} from '@angular/material/sort';
import {Component, ViewChild} from '@angular/core';
+declare global {
+ interface Window {
+ ngDevMode?: object | null;
+ }
+}
+
describe('MatTableDataSource', () => {
describe('sort', () => {
- let dataSource: MatTableDataSource;
+ let dataSource: MatTableDataSource<{'prop': string | number}>;
let fixture: ComponentFixture;
let sort: MatSort;
@@ -18,7 +24,7 @@ describe('MatTableDataSource', () => {
});
/** Test the data source's `sortData` function. */
- function testSortWithValues(values: any[]) {
+ function testSortWithValues(values: (string | number)[]) {
// The data source and MatSort expect the list to contain objects with values, where
// the sort should be performed over a particular key.
// Map the values into an array of objects where each value is keyed by "prop"
@@ -73,13 +79,45 @@ describe('MatTableDataSource', () => {
});
it('should update filteredData even if the data source is disconnected', () => {
- dataSource.data = [1, 2, 3, 4, 5];
- expect(dataSource.filteredData).toEqual([1, 2, 3, 4, 5]);
+ dataSource.data = [{'prop': 1}, {'prop': 2}, {'prop': 3}];
+ expect(dataSource.filteredData).toEqual([{'prop': 1}, {'prop': 2}, {'prop': 3}]);
dataSource.disconnect();
- dataSource.data = [5, 4, 3, 2, 1];
- expect(dataSource.filteredData).toEqual([5, 4, 3, 2, 1]);
+ dataSource.data = [{'prop': 3}, {'prop': 2}, {'prop': 1}];
+ expect(dataSource.filteredData).toEqual([{'prop': 3}, {'prop': 2}, {'prop': 1}]);
});
+
+ it('should filter data', () => {
+ dataSource.data = [{'prop': 1}, {'prop': 'foo'}, {'prop': 'banana'}];
+ dataSource.filter = 'b';
+ expect(dataSource.filteredData).toEqual([{'prop': 'banana'}]);
+ });
+
+ it('does not warn in non-dev mode when filtering non-object data', fakeAsync(() => {
+ const warnSpy = spyOn(console, 'warn');
+ window.ngDevMode = null;
+ dataSource.data = [1, 2, 3, 4, 5] as unknown as {'prop': number}[];
+
+ dataSource.filter = '1';
+ tick();
+
+ expect(warnSpy).not.toHaveBeenCalled();
+ expect(dataSource.filteredData).toEqual([]);
+ }));
+
+ it('displays the warning in dev mode when filtering non-object data', fakeAsync(() => {
+ const warnSpy = spyOn(console, 'warn');
+ window.ngDevMode = {};
+ dataSource.data = [1, 2, 3, 4, 5] as unknown as {'prop': number}[];
+
+ dataSource.filter = '1';
+ tick();
+
+ expect(warnSpy).toHaveBeenCalledWith(
+ jasmine.stringContaining('requires data to be a non-null object'),
+ );
+ expect(dataSource.filteredData).toEqual([]);
+ }));
});
});
diff --git a/src/material/table/table-data-source.ts b/src/material/table/table-data-source.ts
index 984b4d9fef3d..96b944a6d555 100644
--- a/src/material/table/table-data-source.ts
+++ b/src/material/table/table-data-source.ts
@@ -233,6 +233,15 @@ export class MatTableDataSource<
* @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' || data === null)
+ ) {
+ console.warn(
+ 'Default implementation of filterPredicate requires data to be a non-null 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
diff --git a/src/material/table/table.md b/src/material/table/table.md
index 2f9aba0001b1..a46ff2e62214 100644
--- a/src/material/table/table.md
+++ b/src/material/table/table.md
@@ -183,6 +183,10 @@ virtual scrolling which will only render the the visible rows in the DOM as the
To enable virtual scrolling you have to wrap the Material table in a ``
element and add CSS to make the viewport scrollable.
+**Note:** tables with virtual scrolling have the following limitations:
+* `fixedLayout` is always enabled, in order to prevent jumping when rows are swapped out.
+* Conditional templates via the `when` input are [not supported at the moment](https://github.com/angular/components/issues/32670).
+
#### Sorting
diff --git a/src/material/timepicker/timepicker.spec.ts b/src/material/timepicker/timepicker.spec.ts
index be626174ae6a..b351af6ed25a 100644
--- a/src/material/timepicker/timepicker.spec.ts
+++ b/src/material/timepicker/timepicker.spec.ts
@@ -904,6 +904,17 @@ describe('MatTimepicker', () => {
fixture.detectChanges();
expect(input.hasAttribute('aria-activedescendant')).toBe(false);
});
+
+ it('should be able to set classes on the panel', () => {
+ const fixture = TestBed.createComponent(StandaloneTimepicker);
+ fixture.componentInstance.panelClass.set(['foo', 'bar']);
+ fixture.detectChanges();
+ getInput(fixture).click();
+ fixture.detectChanges();
+ const classList = fixture.nativeElement.querySelector('.cdk-overlay-pane').classList;
+ expect(classList).toContain('foo');
+ expect(classList).toContain('bar');
+ });
});
describe('forms integration', () => {
@@ -1415,7 +1426,8 @@ describe('MatTimepicker', () => {
[interval]="interval()"
[options]="customOptions()"
[aria-label]="ariaLabel()"
- [aria-labelledby]="ariaLabelledby()"/>
+ [aria-labelledby]="ariaLabelledby()"
+ [panelClass]="panelClass()"/>
(0);
readonly customOptions = signal[] | null>(null);
readonly openOnClick = signal(true);
+ readonly panelClass = signal([]);
readonly openedSpy = jasmine.createSpy('opened');
readonly closedSpy = jasmine.createSpy('closed');
readonly selectedSpy = jasmine.createSpy('selected');
diff --git a/src/material/timepicker/timepicker.ts b/src/material/timepicker/timepicker.ts
index ca6b04f9a023..3feafc30d24a 100644
--- a/src/material/timepicker/timepicker.ts
+++ b/src/material/timepicker/timepicker.ts
@@ -212,6 +212,9 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
/** Whether the timepicker is currently disabled. */
readonly disabled: Signal = computed(() => !!this._input()?.disabled());
+ /** Classes to be passed to the timepicker panel. */
+ readonly panelClass = input();
+
constructor() {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
validateAdapter(this._dateAdapter, this._dateFormats);
@@ -383,6 +386,7 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
direction: this._dir || 'ltr',
hasBackdrop: false,
disableAnimations: this._animationsDisabled,
+ panelClass: this.panelClass(),
});
this._overlayRef.detachments().subscribe(() => this.close());
diff --git a/src/material/tooltip/BUILD.bazel b/src/material/tooltip/BUILD.bazel
index 448cdb4bf74f..5ff2539e1993 100644
--- a/src/material/tooltip/BUILD.bazel
+++ b/src/material/tooltip/BUILD.bazel
@@ -72,7 +72,6 @@ ng_project(
":css",
],
deps = [
- "//:node_modules/@angular/common",
"//:node_modules/@angular/core",
"//:node_modules/rxjs",
"//src/cdk/coercion",
diff --git a/src/material/tooltip/tooltip.html b/src/material/tooltip/tooltip.html
index b0910db1465f..e9cf9bf15bef 100644
--- a/src/material/tooltip/tooltip.html
+++ b/src/material/tooltip/tooltip.html
@@ -1,7 +1,7 @@