From df28509b643b5821621e173c8cf82d25811b8e87 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 7 Jul 2025 09:13:43 +0200 Subject: [PATCH] fix(material/timepicker): allow timepicker to opt out of opening on click Currently any click on an enabled timepicker input will open the associated timepicker. In some cases that might not be desirable so these changes add an input that allows users to opt out of it. Fixes #31398. --- goldens/material/timepicker/index.api.md | 3 ++- src/material/timepicker/timepicker-input.ts | 12 +++++++++++- src/material/timepicker/timepicker.spec.ts | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/goldens/material/timepicker/index.api.md b/goldens/material/timepicker/index.api.md index f563743b636f..e63bc279182b 100644 --- a/goldens/material/timepicker/index.api.md +++ b/goldens/material/timepicker/index.api.md @@ -94,6 +94,7 @@ export class MatTimepickerInput implements ControlValueAccessor, Validator, O readonly min: InputSignalWithTransform; // (undocumented) ngOnDestroy(): void; + readonly openOnClick: InputSignalWithTransform; registerOnChange(fn: (value: any) => void): void; registerOnTouched(fn: () => void): void; registerOnValidatorChange(fn: () => void): void; @@ -103,7 +104,7 @@ export class MatTimepickerInput implements ControlValueAccessor, Validator, O readonly value: ModelSignal; writeValue(value: any): void; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration, "input[matTimepicker]", ["matTimepickerInput"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "timepicker": { "alias": "matTimepicker"; "required": true; "isSignal": true; }; "min": { "alias": "matTimepickerMin"; "required": false; "isSignal": true; }; "max": { "alias": "matTimepickerMax"; "required": false; "isSignal": true; }; "disabledInput": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>; + static ɵdir: i0.ɵɵDirectiveDeclaration, "input[matTimepicker]", ["matTimepickerInput"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "timepicker": { "alias": "matTimepicker"; "required": true; "isSignal": true; }; "min": { "alias": "matTimepickerMin"; "required": false; "isSignal": true; }; "max": { "alias": "matTimepickerMax"; "required": false; "isSignal": true; }; "openOnClick": { "alias": "matTimepickerOpenOnClick"; "required": false; "isSignal": true; }; "disabledInput": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration, never>; } diff --git a/src/material/timepicker/timepicker-input.ts b/src/material/timepicker/timepicker-input.ts index 3912a236e2d8..93e695139fde 100644 --- a/src/material/timepicker/timepicker-input.ts +++ b/src/material/timepicker/timepicker-input.ts @@ -140,6 +140,16 @@ export class MatTimepickerInput implements ControlValueAccessor, Validator, O transform: (value: unknown) => this._transformDateInput(value), }); + /** + * Whether to open the timepicker overlay when clicking on the input. Enabled by default. + * Note that when disabling this option, you'll have to provide your own logic for opening + * the overlay. + */ + readonly openOnClick: InputSignalWithTransform = input(true, { + alias: 'matTimepickerOpenOnClick', + transform: booleanAttribute, + }); + /** Whether the input is disabled. */ readonly disabled: Signal = computed( () => this.disabledInput() || this._accessorDisabled(), @@ -254,7 +264,7 @@ export class MatTimepickerInput implements ControlValueAccessor, Validator, O /** Handles clicks on the input or the containing form field. */ private _handleClick = (): void => { - if (!this.disabled()) { + if (!this.disabled() && this.openOnClick()) { this.timepicker().open(); } }; diff --git a/src/material/timepicker/timepicker.spec.ts b/src/material/timepicker/timepicker.spec.ts index baf1d2ee7df1..3fa080efb157 100644 --- a/src/material/timepicker/timepicker.spec.ts +++ b/src/material/timepicker/timepicker.spec.ts @@ -480,6 +480,15 @@ describe('MatTimepicker', () => { fixture.detectChanges(); expect(getPanel()).toBeTruthy(); })); + + it('should be able to opt out of opening on click', () => { + const fixture = TestBed.createComponent(StandaloneTimepicker); + fixture.componentInstance.openOnClick.set(false); + fixture.detectChanges(); + getInput(fixture).click(); + fixture.detectChanges(); + expect(getPanel()).toBeFalsy(); + }); }); // Note: these tests intentionally don't cover the full option generation logic @@ -1313,6 +1322,7 @@ describe('MatTimepicker', () => { [disabled]="disabled()" [matTimepickerMin]="min()" [matTimepickerMax]="max()" + [matTimepickerOpenOnClick]="openOnClick()" [value]="value()"/> (false); readonly toggleTabIndex = signal(0); readonly customOptions = signal[] | null>(null); + readonly openOnClick = signal(true); readonly openedSpy = jasmine.createSpy('opened'); readonly closedSpy = jasmine.createSpy('closed'); readonly selectedSpy = jasmine.createSpy('selected');