@@ -12,6 +12,7 @@ import {
1212 booleanAttribute ,
1313 ChangeDetectionStrategy ,
1414 Component ,
15+ computed ,
1516 effect ,
1617 ElementRef ,
1718 inject ,
@@ -104,7 +105,7 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
104105 private _isOpen = signal ( false ) ;
105106 private _activeDescendant = signal < string | null > ( null ) ;
106107
107- private _input : MatTimepickerInput < D > ;
108+ private _input = signal < MatTimepickerInput < D > | null > ( null ) ;
108109 private _overlayRef : OverlayRef | null = null ;
109110 private _portal : TemplatePortal < unknown > | null = null ;
110111 private _optionsCacheKey : string | null = null ;
@@ -174,6 +175,9 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
174175 alias : 'aria-labelledby' ,
175176 } ) ;
176177
178+ /** Whether the timepicker is currently disabled. */
179+ readonly disabled : Signal < boolean > = computed ( ( ) => ! ! this . _input ( ) ?. disabled ( ) ) ;
180+
177181 constructor ( ) {
178182 if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
179183 validateAdapter ( this . _dateAdapter , this . _dateFormats ) ;
@@ -204,14 +208,16 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
204208
205209 /** Opens the timepicker. */
206210 open ( ) : void {
207- if ( ! this . _input ) {
211+ const input = this . _input ( ) ;
212+
213+ if ( ! input ) {
208214 return ;
209215 }
210216
211217 // Focus should already be on the input, but this call is in case the timepicker is opened
212218 // programmatically. We need to call this even if the timepicker is already open, because
213219 // the user might be clicking the toggle.
214- this . _input . focus ( ) ;
220+ input . focus ( ) ;
215221
216222 if ( this . _isOpen ( ) ) {
217223 return ;
@@ -220,14 +226,14 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
220226 this . _isOpen . set ( true ) ;
221227 this . _generateOptions ( ) ;
222228 const overlayRef = this . _getOverlayRef ( ) ;
223- overlayRef . updateSize ( { width : this . _input . getOverlayOrigin ( ) . nativeElement . offsetWidth } ) ;
229+ overlayRef . updateSize ( { width : input . getOverlayOrigin ( ) . nativeElement . offsetWidth } ) ;
224230 this . _portal ??= new TemplatePortal ( this . _panelTemplate ( ) , this . _viewContainerRef ) ;
225231 overlayRef . attach ( this . _portal ) ;
226232 this . _onOpenRender ?. destroy ( ) ;
227233 this . _onOpenRender = afterNextRender (
228234 ( ) => {
229235 const options = this . _options ( ) ;
230- this . _syncSelectedState ( this . _input . value ( ) , options , options [ 0 ] ) ;
236+ this . _syncSelectedState ( input . value ( ) , options , options [ 0 ] ) ;
231237 this . _onOpenRender = null ;
232238 } ,
233239 { injector : this . _injector } ,
@@ -247,11 +253,13 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
247253
248254 /** Registers an input with the timepicker. */
249255 registerInput ( input : MatTimepickerInput < D > ) : void {
250- if ( this . _input && input !== this . _input && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
256+ const currentInput = this . _input ( ) ;
257+
258+ if ( currentInput && input !== currentInput && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
251259 throw new Error ( 'MatTimepicker can only be registered with one input at a time' ) ;
252260 }
253261
254- this . _input = input ;
262+ this . _input . set ( input ) ;
255263 }
256264
257265 ngOnDestroy ( ) : void {
@@ -265,15 +273,15 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
265273 protected _selectValue ( value : D ) {
266274 this . close ( ) ;
267275 this . selected . emit ( { value, source : this } ) ;
268- this . _input . focus ( ) ;
276+ this . _input ( ) ? .focus ( ) ;
269277 }
270278
271279 /** Gets the value of the `aria-labelledby` attribute. */
272280 protected _getAriaLabelledby ( ) : string | null {
273281 if ( this . ariaLabel ( ) ) {
274282 return null ;
275283 }
276- return this . ariaLabelledby ( ) || this . _input ?. _getLabelId ( ) || null ;
284+ return this . ariaLabelledby ( ) || this . _input ( ) ?. _getLabelId ( ) || null ;
277285 }
278286
279287 /** Creates an overlay reference for the timepicker panel. */
@@ -284,7 +292,7 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
284292
285293 const positionStrategy = this . _overlay
286294 . position ( )
287- . flexibleConnectedTo ( this . _input . getOverlayOrigin ( ) )
295+ . flexibleConnectedTo ( this . _input ( ) ! . getOverlayOrigin ( ) )
288296 . withFlexibleDimensions ( false )
289297 . withPush ( false )
290298 . withTransformOriginOn ( '.mat-timepicker-panel' )
@@ -317,9 +325,9 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
317325
318326 this . _overlayRef . outsidePointerEvents ( ) . subscribe ( event => {
319327 const target = _getEventTarget ( event ) as HTMLElement ;
320- const origin = this . _input . getOverlayOrigin ( ) . nativeElement ;
328+ const origin = this . _input ( ) ? .getOverlayOrigin ( ) . nativeElement ;
321329
322- if ( target && target !== origin && ! origin . contains ( target ) ) {
330+ if ( target && origin && target !== origin && ! origin . contains ( target ) ) {
323331 this . close ( ) ;
324332 }
325333 } ) ;
@@ -336,10 +344,11 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
336344 if ( options !== null ) {
337345 this . _timeOptions = options ;
338346 } else {
347+ const input = this . _input ( ) ;
339348 const adapter = this . _dateAdapter ;
340349 const timeFormat = this . _dateFormats . display . timeInput ;
341- const min = this . _input . min ( ) || adapter . setTime ( adapter . today ( ) , 0 , 0 , 0 ) ;
342- const max = this . _input . max ( ) || adapter . setTime ( adapter . today ( ) , 23 , 59 , 0 ) ;
350+ const min = input ? .min ( ) || adapter . setTime ( adapter . today ( ) , 0 , 0 , 0 ) ;
351+ const max = input ? .max ( ) || adapter . setTime ( adapter . today ( ) , 23 , 59 , 0 ) ;
343352 const cacheKey =
344353 interval + '/' + adapter . format ( min , timeFormat ) + '/' + adapter . format ( max , timeFormat ) ;
345354
@@ -432,11 +441,11 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
432441 */
433442 private _handleInputStateChanges ( ) : void {
434443 effect ( ( ) => {
435- const value = this . _input ?. value ( ) ;
444+ const input = this . _input ( ) ;
436445 const options = this . _options ( ) ;
437446
438- if ( this . _isOpen ( ) ) {
439- this . _syncSelectedState ( value , options , null ) ;
447+ if ( this . _isOpen ( ) && input ) {
448+ this . _syncSelectedState ( input . value ( ) , options , null ) ;
440449 }
441450 } ) ;
442451 }
0 commit comments