Skip to content

Commit bfdbc23

Browse files
committed
fix(material/timepicker): unable to reopen if closed by scroll strategy (#30561)
The timepicker wasn't updating its internal state when it gets closed through the overlay which meant that the user can't reopen it. Fixes #30558. (cherry picked from commit 9bc810c)
1 parent f89ed81 commit bfdbc23

File tree

3 files changed

+43
-7
lines changed

3 files changed

+43
-7
lines changed

src/material/timepicker/BUILD.bazel

+3
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,15 @@ ng_test_library(
5454
deps = [
5555
":timepicker",
5656
"//src/cdk/keycodes",
57+
"//src/cdk/overlay",
58+
"//src/cdk/scrolling",
5759
"//src/cdk/testing/private",
5860
"//src/material/core",
5961
"//src/material/form-field",
6062
"//src/material/input",
6163
"@npm//@angular/forms",
6264
"@npm//@angular/platform-browser",
65+
"@npm//rxjs",
6366
],
6467
)
6568

src/material/timepicker/timepicker.spec.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Component, Provider, signal, ViewChild} from '@angular/core';
1+
import {Component, inject, Provider, signal, ViewChild} from '@angular/core';
22
import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
33
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
44
import {DateAdapter, provideNativeDateAdapter} from '@angular/material/core';
@@ -24,10 +24,13 @@ import {
2424
import {MatInput} from '@angular/material/input';
2525
import {MatFormField, MatLabel, MatSuffix} from '@angular/material/form-field';
2626
import {MatTimepickerInput} from './timepicker-input';
27-
import {MatTimepicker} from './timepicker';
27+
import {MAT_TIMEPICKER_SCROLL_STRATEGY, MatTimepicker} from './timepicker';
2828
import {MatTimepickerToggle} from './timepicker-toggle';
2929
import {MAT_TIMEPICKER_CONFIG, MatTimepickerOption} from './util';
3030
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
31+
import {ScrollDispatcher} from '@angular/cdk/scrolling';
32+
import {Overlay} from '@angular/cdk/overlay';
33+
import {Subject} from 'rxjs';
3134

3235
describe('MatTimepicker', () => {
3336
let adapter: DateAdapter<Date>;
@@ -440,6 +443,38 @@ describe('MatTimepicker', () => {
440443
flush();
441444
}).not.toThrow();
442445
}));
446+
447+
it('should be able to reopen the panel when closed by a scroll strategy', fakeAsync(() => {
448+
const scrolledSubject = new Subject();
449+
450+
TestBed.resetTestingModule();
451+
configureTestingModule([
452+
{
453+
provide: ScrollDispatcher,
454+
useValue: {scrolled: () => scrolledSubject},
455+
},
456+
{
457+
provide: MAT_TIMEPICKER_SCROLL_STRATEGY,
458+
useFactory: () => {
459+
const overlay = inject(Overlay);
460+
return () => overlay.scrollStrategies.close();
461+
},
462+
},
463+
]);
464+
465+
const fixture = TestBed.createComponent(StandaloneTimepicker);
466+
fixture.detectChanges();
467+
fixture.componentInstance.timepicker.open();
468+
fixture.detectChanges();
469+
expect(getPanel()).toBeTruthy();
470+
scrolledSubject.next();
471+
fixture.detectChanges();
472+
flush();
473+
expect(getPanel()).toBeFalsy();
474+
fixture.componentInstance.timepicker.open();
475+
fixture.detectChanges();
476+
expect(getPanel()).toBeTruthy();
477+
}));
443478
});
444479

445480
// Note: these tests intentionally don't cover the full option generation logic

src/material/timepicker/timepicker.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export const MAT_TIMEPICKER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStr
7070
providedIn: 'root',
7171
factory: () => {
7272
const overlay = inject(Overlay);
73-
return () => overlay.scrollStrategies.reposition();
73+
return () => overlay.scrollStrategies.close();
7474
},
7575
},
7676
);
@@ -340,10 +340,8 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
340340
hasBackdrop: false,
341341
});
342342

343-
this._overlayRef.keydownEvents().subscribe(event => {
344-
this._handleKeydown(event);
345-
});
346-
343+
this._overlayRef.detachments().subscribe(() => this.close());
344+
this._overlayRef.keydownEvents().subscribe(event => this._handleKeydown(event));
347345
this._overlayRef.outsidePointerEvents().subscribe(event => {
348346
const target = _getEventTarget(event) as HTMLElement;
349347
const origin = this._input()?.getOverlayOrigin().nativeElement;

0 commit comments

Comments
 (0)