Skip to content

Commit

Permalink
fix(material/timepicker): switch away from animations module (#30404)
Browse files Browse the repository at this point in the history
Reworks the timepicker to move it away from the animations module for the dropdown animation.
  • Loading branch information
crisbeto authored Jan 28, 2025
1 parent d84a4c3 commit 992aff1
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 20 deletions.
6 changes: 4 additions & 2 deletions src/material/timepicker/timepicker.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
<div
role="listbox"
class="mat-timepicker-panel"
[class.mat-timepicker-panel-animations-enabled]="!_animationsDisabled"
[class.mat-timepicker-panel-exit]="!isOpen()"
[attr.aria-label]="ariaLabel() || null"
[attr.aria-labelledby]="_getAriaLabelledby()"
[id]="panelId"
@panel>
(animationend)="_handleAnimationEnd($event)">
@for (option of _timeOptions; track option.value) {
<mat-option
[value]="option.value"
(onSelectionChange)="_selectValue(option.value)">{{option.label}}</mat-option>
(onSelectionChange)="_selectValue($event.source)">{{option.label}}</mat-option>
}
</div>
</ng-template>
30 changes: 30 additions & 0 deletions src/material/timepicker/timepicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
41 changes: 24 additions & 17 deletions src/material/timepicker/timepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {
afterNextRender,
AfterRenderRef,
ANIMATION_MODULE_TYPE,
booleanAttribute,
ChangeDetectionStrategy,
Component,
Expand All @@ -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,
Expand Down Expand Up @@ -80,18 +80,6 @@ export interface MatTimepickerSelected<D> {
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<D> implements OnDestroy, MatOptionParentComponent {
private _overlay = inject(Overlay);
Expand All @@ -101,6 +89,8 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
private _defaultConfig = inject(MAT_TIMEPICKER_CONFIG, {optional: true});
private _dateAdapter = inject<DateAdapter<D>>(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<string | null>(null);
Expand Down Expand Up @@ -246,8 +236,11 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
close(): void {
if (this._isOpen()) {
this._isOpen.set(false);
this._overlayRef?.detach();
this.closed.emit();

if (this._animationsDisabled) {
this._overlayRef?.detach();
}
}
}

Expand All @@ -270,9 +263,16 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
}

/** Selects a specific time value. */
protected _selectValue(value: D) {
protected _selectValue(option: MatOption<D>) {
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();
}

Expand All @@ -284,6 +284,13 @@ export class MatTimepicker<D> 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) {
Expand Down Expand Up @@ -409,7 +416,7 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
event.preventDefault();

if (this._keyManager.activeItem) {
this._selectValue(this._keyManager.activeItem.value);
this._selectValue(this._keyManager.activeItem);
} else {
this.close();
}
Expand Down
5 changes: 4 additions & 1 deletion tools/public_api_guard/material/timepicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ export const MAT_TIMEPICKER_CONFIG: InjectionToken<MatTimepickerConfig>;
export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
constructor();
readonly activeDescendant: Signal<string | null>;
// (undocumented)
protected _animationsDisabled: boolean;
readonly ariaLabel: InputSignal<string | null>;
readonly ariaLabelledby: InputSignal<string | null>;
close(): void;
readonly closed: OutputEmitterRef<void>;
readonly disabled: Signal<boolean>;
readonly disableRipple: InputSignalWithTransform<boolean, unknown>;
protected _getAriaLabelledby(): string | null;
protected _handleAnimationEnd(event: AnimationEvent): void;
readonly interval: InputSignalWithTransform<number | null, number | string | null>;
readonly isOpen: Signal<boolean>;
// (undocumented)
Expand All @@ -50,7 +53,7 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
protected _panelTemplate: Signal<TemplateRef<unknown>>;
registerInput(input: MatTimepickerInput<D>): void;
readonly selected: OutputEmitterRef<MatTimepickerSelected<D>>;
protected _selectValue(value: D): void;
protected _selectValue(option: MatOption<D>): void;
// (undocumented)
protected _timeOptions: readonly MatTimepickerOption<D>[];
// (undocumented)
Expand Down

0 comments on commit 992aff1

Please sign in to comment.