diff --git a/src/material/timepicker/timepicker.html b/src/material/timepicker/timepicker.html
index 86d86a41a89e..7e248eae11f4 100644
--- a/src/material/timepicker/timepicker.html
+++ b/src/material/timepicker/timepicker.html
@@ -2,14 +2,16 @@
+ (animationend)="_handleAnimationEnd($event)">
@for (option of _timeOptions; track option.value) {
{{option.label}}
+ (onSelectionChange)="_selectValue($event.source)">{{option.label}}
}
diff --git a/src/material/timepicker/timepicker.scss b/src/material/timepicker/timepicker.scss
index 4cea2ec88ebc..fd98d9c01294 100644
--- a/src/material/timepicker/timepicker.scss
+++ b/src/material/timepicker/timepicker.scss
@@ -2,6 +2,28 @@
@use '../core/tokens/token-utils';
@use '../core/tokens/m2/mat/timepicker' as tokens-mat-timepicker;
+@keyframes _mat-timepicker-enter {
+ from {
+ opacity: 0;
+ transform: scaleY(0.8);
+ }
+
+ to {
+ opacity: 1;
+ transform: none;
+ }
+}
+
+@keyframes _mat-timepicker-exit {
+ from {
+ opacity: 1;
+ }
+
+ to {
+ opacity: 0;
+ }
+}
+
mat-timepicker {
display: none;
}
@@ -38,6 +60,14 @@ mat-timepicker {
}
}
+.mat-timepicker-panel-animations-enabled {
+ animation: _mat-timepicker-enter 120ms cubic-bezier(0, 0, 0.2, 1);
+
+ &.mat-timepicker-panel-exit {
+ animation: _mat-timepicker-exit 100ms linear;
+ }
+}
+
.mat-timepicker-input[readonly] {
cursor: pointer;
}
diff --git a/src/material/timepicker/timepicker.ts b/src/material/timepicker/timepicker.ts
index 2a32ca0837b9..9d538213a793 100644
--- a/src/material/timepicker/timepicker.ts
+++ b/src/material/timepicker/timepicker.ts
@@ -9,6 +9,7 @@
import {
afterNextRender,
AfterRenderRef,
+ ANIMATION_MODULE_TYPE,
booleanAttribute,
ChangeDetectionStrategy,
Component,
@@ -32,7 +33,6 @@ import {
ViewContainerRef,
ViewEncapsulation,
} from '@angular/core';
-import {animate, group, state, style, transition, trigger} from '@angular/animations';
import {
DateAdapter,
MAT_DATE_FORMATS,
@@ -80,18 +80,6 @@ export interface MatTimepickerSelected {
useExisting: MatTimepicker,
},
],
- animations: [
- trigger('panel', [
- state('void', style({opacity: 0, transform: 'scaleY(0.8)'})),
- transition(':enter', [
- group([
- animate('0.03s linear', style({opacity: 1})),
- animate('0.12s cubic-bezier(0, 0, 0.2, 1)', style({transform: 'scaleY(1)'})),
- ]),
- ]),
- transition(':leave', [animate('0.075s linear', style({opacity: 0}))]),
- ]),
- ],
})
export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
private _overlay = inject(Overlay);
@@ -101,6 +89,8 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
private _defaultConfig = inject(MAT_TIMEPICKER_CONFIG, {optional: true});
private _dateAdapter = inject>(DateAdapter, {optional: true})!;
private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!;
+ protected _animationsDisabled =
+ inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations';
private _isOpen = signal(false);
private _activeDescendant = signal(null);
@@ -246,8 +236,11 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
close(): void {
if (this._isOpen()) {
this._isOpen.set(false);
- this._overlayRef?.detach();
this.closed.emit();
+
+ if (this._animationsDisabled) {
+ this._overlayRef?.detach();
+ }
}
}
@@ -270,9 +263,16 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
}
/** Selects a specific time value. */
- protected _selectValue(value: D) {
+ protected _selectValue(option: MatOption) {
this.close();
- this.selected.emit({value, source: this});
+ this._keyManager.setActiveItem(option);
+ this._options().forEach(current => {
+ // This is primarily here so we don't show two selected options while animating away.
+ if (current !== option) {
+ current.deselect(false);
+ }
+ });
+ this.selected.emit({value: option.value, source: this});
this._input()?.focus();
}
@@ -284,6 +284,13 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
return this.ariaLabelledby() || this._input()?._getLabelId() || null;
}
+ /** Handles animation events coming from the panel. */
+ protected _handleAnimationEnd(event: AnimationEvent) {
+ if (event.animationName === '_mat-timepicker-exit') {
+ this._overlayRef?.detach();
+ }
+ }
+
/** Creates an overlay reference for the timepicker panel. */
private _getOverlayRef(): OverlayRef {
if (this._overlayRef) {
@@ -409,7 +416,7 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
event.preventDefault();
if (this._keyManager.activeItem) {
- this._selectValue(this._keyManager.activeItem.value);
+ this._selectValue(this._keyManager.activeItem);
} else {
this.close();
}
diff --git a/tools/public_api_guard/material/timepicker.md b/tools/public_api_guard/material/timepicker.md
index d99141df8ddc..d5c868014967 100644
--- a/tools/public_api_guard/material/timepicker.md
+++ b/tools/public_api_guard/material/timepicker.md
@@ -29,6 +29,8 @@ export const MAT_TIMEPICKER_CONFIG: InjectionToken;
export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
constructor();
readonly activeDescendant: Signal;
+ // (undocumented)
+ protected _animationsDisabled: boolean;
readonly ariaLabel: InputSignal;
readonly ariaLabelledby: InputSignal;
close(): void;
@@ -36,6 +38,7 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
readonly disabled: Signal;
readonly disableRipple: InputSignalWithTransform;
protected _getAriaLabelledby(): string | null;
+ protected _handleAnimationEnd(event: AnimationEvent): void;
readonly interval: InputSignalWithTransform;
readonly isOpen: Signal;
// (undocumented)
@@ -50,7 +53,7 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent {
protected _panelTemplate: Signal>;
registerInput(input: MatTimepickerInput): void;
readonly selected: OutputEmitterRef>;
- protected _selectValue(value: D): void;
+ protected _selectValue(option: MatOption): void;
// (undocumented)
protected _timeOptions: readonly MatTimepickerOption[];
// (undocumented)