diff --git a/src/material/timepicker/BUILD.bazel b/src/material/timepicker/BUILD.bazel index a46395de2a01..880e63549c0a 100644 --- a/src/material/timepicker/BUILD.bazel +++ b/src/material/timepicker/BUILD.bazel @@ -54,12 +54,15 @@ ng_test_library( deps = [ ":timepicker", "//src/cdk/keycodes", + "//src/cdk/overlay", + "//src/cdk/scrolling", "//src/cdk/testing/private", "//src/material/core", "//src/material/form-field", "//src/material/input", "@npm//@angular/forms", "@npm//@angular/platform-browser", + "@npm//rxjs", ], ) diff --git a/src/material/timepicker/timepicker.spec.ts b/src/material/timepicker/timepicker.spec.ts index da1bd81e5365..9182f2efed11 100644 --- a/src/material/timepicker/timepicker.spec.ts +++ b/src/material/timepicker/timepicker.spec.ts @@ -1,4 +1,4 @@ -import {Component, Provider, signal, ViewChild} from '@angular/core'; +import {Component, inject, Provider, signal, ViewChild} from '@angular/core'; import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {DateAdapter, provideNativeDateAdapter} from '@angular/material/core'; @@ -24,10 +24,13 @@ import { import {MatInput} from '@angular/material/input'; import {MatFormField, MatLabel, MatSuffix} from '@angular/material/form-field'; import {MatTimepickerInput} from './timepicker-input'; -import {MatTimepicker} from './timepicker'; +import {MAT_TIMEPICKER_SCROLL_STRATEGY, MatTimepicker} from './timepicker'; import {MatTimepickerToggle} from './timepicker-toggle'; import {MAT_TIMEPICKER_CONFIG, MatTimepickerOption} from './util'; import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms'; +import {ScrollDispatcher} from '@angular/cdk/scrolling'; +import {Overlay} from '@angular/cdk/overlay'; +import {Subject} from 'rxjs'; describe('MatTimepicker', () => { let adapter: DateAdapter; @@ -440,6 +443,38 @@ describe('MatTimepicker', () => { flush(); }).not.toThrow(); })); + + it('should be able to reopen the panel when closed by a scroll strategy', fakeAsync(() => { + const scrolledSubject = new Subject(); + + TestBed.resetTestingModule(); + configureTestingModule([ + { + provide: ScrollDispatcher, + useValue: {scrolled: () => scrolledSubject}, + }, + { + provide: MAT_TIMEPICKER_SCROLL_STRATEGY, + useFactory: () => { + const overlay = inject(Overlay); + return () => overlay.scrollStrategies.close(); + }, + }, + ]); + + const fixture = TestBed.createComponent(StandaloneTimepicker); + fixture.detectChanges(); + fixture.componentInstance.timepicker.open(); + fixture.detectChanges(); + expect(getPanel()).toBeTruthy(); + scrolledSubject.next(); + fixture.detectChanges(); + flush(); + expect(getPanel()).toBeFalsy(); + fixture.componentInstance.timepicker.open(); + fixture.detectChanges(); + expect(getPanel()).toBeTruthy(); + })); }); // Note: these tests intentionally don't cover the full option generation logic diff --git a/src/material/timepicker/timepicker.ts b/src/material/timepicker/timepicker.ts index c11b5cabc1d0..97df89a24d1a 100644 --- a/src/material/timepicker/timepicker.ts +++ b/src/material/timepicker/timepicker.ts @@ -70,7 +70,7 @@ export const MAT_TIMEPICKER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStr providedIn: 'root', factory: () => { const overlay = inject(Overlay); - return () => overlay.scrollStrategies.reposition(); + return () => overlay.scrollStrategies.close(); }, }, ); @@ -340,10 +340,8 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent { hasBackdrop: false, }); - this._overlayRef.keydownEvents().subscribe(event => { - this._handleKeydown(event); - }); - + this._overlayRef.detachments().subscribe(() => this.close()); + this._overlayRef.keydownEvents().subscribe(event => this._handleKeydown(event)); this._overlayRef.outsidePointerEvents().subscribe(event => { const target = _getEventTarget(event) as HTMLElement; const origin = this._input()?.getOverlayOrigin().nativeElement;