From 18ca9b0ea6be60144df11c50ca40d5d37dcb28ad Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 8 Apr 2021 23:18:00 +0200 Subject: [PATCH] feat(material/datepicker): remove dependency on dialog Currently the datepicker has a hard dependency on `MatDialog` due to its touch-specific appearance which makes it difficult to use together with the MDC packages, because they have an alternate dialog module. These changes resolve the issue by removing the datepicker's dependency on `MatDialog`. The approach is loosely based on #13019, however I've simplified things a bit to make it more maintainable. --- src/material/datepicker/BUILD.bazel | 2 - .../datepicker/_datepicker-theme.scss | 2 +- src/material/datepicker/calendar-body.scss | 6 - .../datepicker/datepicker-animations.ts | 17 +- src/material/datepicker/datepicker-base.ts | 235 ++++++++---------- .../datepicker/datepicker-content.scss | 10 +- src/material/datepicker/datepicker-module.ts | 2 - src/material/datepicker/datepicker.spec.ts | 63 ++--- .../public_api_guard/material/datepicker.d.ts | 4 +- 9 files changed, 142 insertions(+), 199 deletions(-) diff --git a/src/material/datepicker/BUILD.bazel b/src/material/datepicker/BUILD.bazel index e16ded85e99a..cb8d9f65b696 100644 --- a/src/material/datepicker/BUILD.bazel +++ b/src/material/datepicker/BUILD.bazel @@ -35,7 +35,6 @@ ng_module( "//src/cdk/portal", "//src/material/button", "//src/material/core", - "//src/material/dialog", "//src/material/form-field", "//src/material/input", "@npm//@angular/animations", @@ -102,7 +101,6 @@ ng_test_library( "//src/cdk/scrolling", "//src/cdk/testing/private", "//src/material/core", - "//src/material/dialog", "//src/material/form-field", "//src/material/input", "//src/material/testing", diff --git a/src/material/datepicker/_datepicker-theme.scss b/src/material/datepicker/_datepicker-theme.scss index 1e358df7c2c4..5549f69b64c0 100644 --- a/src/material/datepicker/_datepicker-theme.scss +++ b/src/material/datepicker/_datepicker-theme.scss @@ -163,7 +163,7 @@ $calendar-weekday-table-font-size: 11px !default; } .mat-datepicker-content-touch { - @include private.private-theme-elevation(0, $config); + @include private.private-theme-elevation(24, $config); } .mat-datepicker-toggle-active { diff --git a/src/material/datepicker/calendar-body.scss b/src/material/datepicker/calendar-body.scss index d4236eaae907..6af6f522b3eb 100644 --- a/src/material/datepicker/calendar-body.scss +++ b/src/material/datepicker/calendar-body.scss @@ -192,12 +192,6 @@ $calendar-range-end-body-cell-size: } } -// Allows for the screen reader close button to be seen in touch UI mode. -.mat-datepicker-dialog .mat-dialog-container { - position: relative; - overflow: visible; -} - @include a11y.high-contrast(active, off) { .mat-datepicker-popup:not(:empty), .mat-calendar-body-selected { diff --git a/src/material/datepicker/datepicker-animations.ts b/src/material/datepicker/datepicker-animations.ts index 1da9d55be6e0..48b4e8667785 100644 --- a/src/material/datepicker/datepicker-animations.ts +++ b/src/material/datepicker/datepicker-animations.ts @@ -11,6 +11,7 @@ import { style, transition, trigger, + keyframes, AnimationTriggerMetadata, } from '@angular/animations'; @@ -24,14 +25,14 @@ export const matDatepickerAnimations: { } = { /** Transforms the height of the datepicker's calendar. */ transformPanel: trigger('transformPanel', [ - state('void', style({ - opacity: 0, - transform: 'scale(1, 0.8)' - })), - transition('void => enter', animate('120ms cubic-bezier(0, 0, 0.2, 1)', style({ - opacity: 1, - transform: 'scale(1, 1)' - }))), + transition('void => enter-dropdown', animate('120ms cubic-bezier(0, 0, 0.2, 1)', keyframes([ + style({opacity: 0, transform: 'scale(1, 0.8)'}), + style({opacity: 1, transform: 'scale(1, 1)'}) + ]))), + transition('void => enter-dialog', animate('150ms cubic-bezier(0, 0, 0.2, 1)', keyframes([ + style({opacity: 0, transform: 'scale(0.7)'}), + style({transform: 'none', opacity: 1}) + ]))), transition('* => void', animate('100ms linear', style({opacity: 0}))) ]), diff --git a/src/material/datepicker/datepicker-base.ts b/src/material/datepicker/datepicker-base.ts index 012101e95b41..bf5def8e8964 100644 --- a/src/material/datepicker/datepicker-base.ts +++ b/src/material/datepicker/datepicker-base.ts @@ -48,7 +48,6 @@ import { mixinColor, ThemePalette, } from '@angular/material/core'; -import {MatDialog, MatDialogRef} from '@angular/material/dialog'; import {merge, Subject, Observable, Subscription} from 'rxjs'; import {filter, take} from 'rxjs/operators'; import {MatCalendar, MatCalendarView} from './calendar'; @@ -101,9 +100,9 @@ const _MatDatepickerContentMixinBase: CanColorCtor & typeof MatDatepickerContent mixinColor(MatDatepickerContentBase); /** - * Component used as the content for the datepicker dialog and popup. We use this instead of using + * Component used as the content for the datepicker overlay. We use this instead of using * MatCalendar directly as the content so we can control the initial focus. This also gives us a - * place to put additional features of the popup that are not part of the calendar itself in the + * place to put additional features of the overlay that are not part of the calendar itself in the * future. (e.g. confirmation buttons). * @docs-private */ @@ -147,7 +146,7 @@ export class MatDatepickerContent> _isAbove: boolean; /** Current state of the animation. */ - _animationState: 'enter' | 'void' = 'enter'; + _animationState: 'enter-dropdown' | 'enter-dialog' | 'void'; /** Emits when an animation has finished. */ readonly _animationDone = new Subject(); @@ -178,6 +177,7 @@ export class MatDatepickerContent> // otherwise update the global model directly. Note that we want to assign this as soon as // possible, but `_actionsPortal` isn't available in the constructor so we do it in `ngOnInit`. this._model = this._actionsPortal ? this._globalModel.clone() : this._globalModel; + this._animationState = this.datepicker.touchUi ? 'enter-dialog' : 'enter-dropdown'; } ngAfterViewInit() { @@ -211,7 +211,7 @@ export class MatDatepickerContent> this._model.add(value); } - // Delegate closing the popup to the actions. + // Delegate closing the overlay to the actions. if ((!this._model || this._model.isComplete()) && !this._actionsPortal) { this.datepicker.close(); } @@ -310,7 +310,7 @@ export abstract class MatDatepickerBase, S, /** * Whether the calendar UI is in touch mode. In touch mode the calendar opens in a dialog rather - * than a popup and elements have more padding to allow for bigger touch targets. + * than a dropdown and elements have more padding to allow for bigger touch targets. */ @Input() get touchUi(): boolean { return this._touchUi; } @@ -418,14 +418,11 @@ export abstract class MatDatepickerBase, S, return this.datepickerInput && this.datepickerInput.dateFilter; } - /** A reference to the overlay when the calendar is opened as a popup. */ - private _popupRef: OverlayRef | null; + /** A reference to the overlay into which we've rendered the calendar. */ + private _overlayRef: OverlayRef | null; - /** A reference to the dialog when the calendar is opened as a dialog. */ - private _dialogRef: MatDialogRef> | null; - - /** Reference to the component instantiated in popup mode. */ - private _popupComponentRef: ComponentRef> | null; + /** Reference to the component instance rendered in the overlay. */ + private _componentRef: ComponentRef> | null; /** The element that was focused before the datepicker was opened. */ private _focusedElementBeforeOpen: HTMLElement | null = null; @@ -442,15 +439,20 @@ export abstract class MatDatepickerBase, S, /** Emits when the datepicker's state changes. */ readonly stateChanges = new Subject(); - constructor(private _dialog: MatDialog, - private _overlay: Overlay, - private _ngZone: NgZone, - private _viewContainerRef: ViewContainerRef, - @Inject(MAT_DATEPICKER_SCROLL_STRATEGY) scrollStrategy: any, - @Optional() private _dateAdapter: DateAdapter, - @Optional() private _dir: Directionality, - @Optional() @Inject(DOCUMENT) private _document: any, - private _model: MatDateSelectionModel) { + constructor( + /** + * @deprecated `_dialog` parameter is no longer being used and it will be removed. + * @breaking-change 13.0.0 + */ + @Inject(ElementRef) _dialog: any, + private _overlay: Overlay, + private _ngZone: NgZone, + private _viewContainerRef: ViewContainerRef, + @Inject(MAT_DATEPICKER_SCROLL_STRATEGY) scrollStrategy: any, + @Optional() private _dateAdapter: DateAdapter, + @Optional() private _dir: Directionality, + @Optional() @Inject(DOCUMENT) private _document: any, + private _model: MatDateSelectionModel) { if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw createMissingDateImplError('DateAdapter'); } @@ -461,12 +463,15 @@ export abstract class MatDatepickerBase, S, ngOnChanges(changes: SimpleChanges) { const positionChange = changes['xPosition'] || changes['yPosition']; - if (positionChange && !positionChange.firstChange && this._popupRef) { - this._setConnectedPositions( - this._popupRef.getConfig().positionStrategy as FlexibleConnectedPositionStrategy); + if (positionChange && !positionChange.firstChange && this._overlayRef) { + const positionStrategy = this._overlayRef.getConfig().positionStrategy; - if (this.opened) { - this._popupRef.updatePosition(); + if (positionStrategy instanceof FlexibleConnectedPositionStrategy) { + this._setConnectedPositions(positionStrategy); + + if (this.opened) { + this._overlayRef.updatePosition(); + } } } @@ -474,7 +479,7 @@ export abstract class MatDatepickerBase, S, } ngOnDestroy() { - this._destroyPopup(); + this._destroyOverlay(); this.close(); this._inputStateChanges.unsubscribe(); this.stateChanges.complete(); @@ -542,6 +547,7 @@ export abstract class MatDatepickerBase, S, if (this._opened || this.disabled) { return; } + if (!this.datepickerInput && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw Error('Attempted to open an MatDatepicker with no associated input.'); } @@ -551,7 +557,7 @@ export abstract class MatDatepickerBase, S, const activeElement: HTMLElement|null = this._document?.activeElement; this._focusedElementBeforeOpen = activeElement?.shadowRoot?.activeElement as HTMLElement || activeElement; - this.touchUi ? this._openAsDialog() : this._openAsPopup(); + this._openOverlay(); this._opened = true; this.openedStream.emit(); } @@ -561,14 +567,11 @@ export abstract class MatDatepickerBase, S, if (!this._opened) { return; } - if (this._popupComponentRef && this._popupRef) { - const instance = this._popupComponentRef.instance; + + if (this._componentRef) { + const instance = this._componentRef.instance; instance._startExitAnimation(); - instance._animationDone.pipe(take(1)).subscribe(() => this._destroyPopup()); - } - if (this._dialogRef) { - this._dialogRef.close(); - this._dialogRef = null; + instance._animationDone.pipe(take(1)).subscribe(() => this._destroyOverlay()); } const completeClose = () => { @@ -595,71 +598,9 @@ export abstract class MatDatepickerBase, S, } } - /** Applies the current pending selection on the popup to the model. */ + /** Applies the current pending selection on the overlay to the model. */ _applyPendingSelection() { - const instance = this._popupComponentRef?.instance || this._dialogRef?.componentInstance; - instance?._applyPendingSelection(); - } - - /** Open the calendar as a dialog. */ - private _openAsDialog(): void { - // Usually this would be handled by `open` which ensures that we can only have one overlay - // open at a time, however since we reset the variables in async handlers some overlays - // may slip through if the user opens and closes multiple times in quick succession (e.g. - // by holding down the enter key). - if (this._dialogRef) { - this._dialogRef.close(); - } - - this._dialogRef = this._dialog.open>(MatDatepickerContent, { - direction: this._dir ? this._dir.value : 'ltr', - viewContainerRef: this._viewContainerRef, - panelClass: 'mat-datepicker-dialog', - - // These values are all the same as the defaults, but we set them explicitly so that the - // datepicker dialog behaves consistently even if the user changed the defaults. - hasBackdrop: true, - disableClose: false, - backdropClass: ['cdk-overlay-dark-backdrop', this._backdropHarnessClass], - width: '', - height: '', - minWidth: '', - minHeight: '', - maxWidth: '80vw', - maxHeight: '', - position: {}, - - // Disable the dialog's automatic focus capturing, because it'll go to the close button - // automatically. The calendar will move focus on its own once it renders. - autoFocus: false, - - // `MatDialog` has focus restoration built in, however we want to disable it since the - // datepicker also has focus restoration for dropdown mode. We want to do this, in order - // to ensure that the timing is consistent between dropdown and dialog modes since `MatDialog` - // restores focus when the animation is finished, but the datepicker does it immediately. - // Furthermore, this avoids any conflicts where the datepicker consumer might move focus - // inside the `closed` event which is dispatched immediately. - restoreFocus: false - }); - - this._dialogRef.afterClosed().subscribe(() => this.close()); - this._forwardContentValues(this._dialogRef.componentInstance); - } - - /** Open the calendar as a popup. */ - private _openAsPopup(): void { - const portal = new ComponentPortal>(MatDatepickerContent, - this._viewContainerRef); - - this._destroyPopup(); - this._createPopup(); - this._popupComponentRef = this._popupRef!.attach(portal); - this._forwardContentValues(this._popupComponentRef.instance); - - // Update the position once the calendar has rendered. - this._ngZone.onStable.pipe(take(1)).subscribe(() => { - this._popupRef!.updatePosition(); - }); + this._componentRef?.instance?._applyPendingSelection(); } /** Forwards relevant values from the datepicker to the datepicker content inside the overlay. */ @@ -669,52 +610,71 @@ export abstract class MatDatepickerBase, S, instance._actionsPortal = this._actionsPortal; } - /** Create the popup. */ - private _createPopup(): void { - const positionStrategy = this._overlay.position() - .flexibleConnectedTo(this.datepickerInput.getConnectedOverlayOrigin()) - .withTransformOriginOn('.mat-datepicker-content') - .withFlexibleDimensions(false) - .withViewportMargin(8) - .withLockedPosition(); + /** Opens the overlay with the calendar. */ + private _openOverlay(): void { + this._destroyOverlay(); - const overlayConfig = new OverlayConfig({ - positionStrategy: this._setConnectedPositions(positionStrategy), + const isDialog = this.touchUi; + const portal = new ComponentPortal>(MatDatepickerContent, + this._viewContainerRef); + const overlayRef = this._overlayRef = this._overlay.create(new OverlayConfig({ + positionStrategy: isDialog ? this._getDialogStrategy() : this._getDropdownStrategy(), hasBackdrop: true, - backdropClass: ['mat-overlay-transparent-backdrop', this._backdropHarnessClass], + backdropClass: [ + isDialog ? 'cdk-overlay-dark-backdrop' : 'mat-overlay-transparent-backdrop', + this._backdropHarnessClass + ], direction: this._dir, - scrollStrategy: this._scrollStrategy(), - panelClass: 'mat-datepicker-popup', - }); + scrollStrategy: isDialog ? this._overlay.scrollStrategies.block() : this._scrollStrategy(), + panelClass: `mat-datepicker-${isDialog ? 'dialog' : 'popup'}`, + })); + overlayRef.overlayElement.setAttribute('role', 'dialog'); - this._popupRef = this._overlay.create(overlayConfig); - this._popupRef.overlayElement.setAttribute('role', 'dialog'); + if (isDialog) { + overlayRef.overlayElement.setAttribute('aria-modal', 'true'); + } - merge( - this._popupRef.backdropClick(), - this._popupRef.detachments(), - this._popupRef.keydownEvents().pipe(filter(event => { - // Closing on alt + up is only valid when there's an input associated with the datepicker. - return (event.keyCode === ESCAPE && !hasModifierKey(event)) || (this.datepickerInput && - hasModifierKey(event, 'altKey') && event.keyCode === UP_ARROW); - })) - ).subscribe(event => { + this._getCloseStream(overlayRef).subscribe(event => { if (event) { event.preventDefault(); } - this.close(); }); + + this._componentRef = overlayRef.attach(portal); + this._forwardContentValues(this._componentRef.instance); + + // Update the position once the calendar has rendered. Only relevant in dropdown mode. + if (!isDialog) { + this._ngZone.onStable.pipe(take(1)).subscribe(() => overlayRef.updatePosition()); + } } - /** Destroys the current popup overlay. */ - private _destroyPopup() { - if (this._popupRef) { - this._popupRef.dispose(); - this._popupRef = this._popupComponentRef = null; + /** Destroys the current overlay. */ + private _destroyOverlay() { + if (this._overlayRef) { + this._overlayRef.dispose(); + this._overlayRef = this._componentRef = null; } } + /** Gets a position strategy that will open the calendar as a dropdown. */ + private _getDialogStrategy() { + return this._overlay.position().global().centerHorizontally().centerVertically(); + } + + /** Gets a position strategy that will open the calendar as a dropdown. */ + private _getDropdownStrategy() { + const strategy = this._overlay.position() + .flexibleConnectedTo(this.datepickerInput.getConnectedOverlayOrigin()) + .withTransformOriginOn('.mat-datepicker-content') + .withFlexibleDimensions(false) + .withViewportMargin(8) + .withLockedPosition(); + + return this._setConnectedPositions(strategy); + } + /** Sets the positions of the datepicker in dropdown mode based on the current configuration. */ private _setConnectedPositions(strategy: FlexibleConnectedPositionStrategy) { const primaryX = this.xPosition === 'end' ? 'end' : 'start'; @@ -750,6 +710,19 @@ export abstract class MatDatepickerBase, S, ]); } + /** Gets an observable that will emit when the overlay is supposed to be closed. */ + private _getCloseStream(overlayRef: OverlayRef) { + return merge( + overlayRef.backdropClick(), + overlayRef.detachments(), + overlayRef.keydownEvents().pipe(filter(event => { + // Closing on alt + up is only valid when there's an input associated with the datepicker. + return (event.keyCode === ESCAPE && !hasModifierKey(event)) || (this.datepickerInput && + hasModifierKey(event, 'altKey') && event.keyCode === UP_ARROW); + })) + ); + } + static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_opened: BooleanInput; static ngAcceptInputType_touchUi: BooleanInput; diff --git a/src/material/datepicker/datepicker-content.scss b/src/material/datepicker/datepicker-content.scss index bfb51aaccd59..4ffd85714503 100644 --- a/src/material/datepicker/datepicker-content.scss +++ b/src/material/datepicker/datepicker-content.scss @@ -57,13 +57,13 @@ $touch-max-height: 788px; .mat-datepicker-content-touch { display: block; - // Make sure the dialog scrolls rather than being cropped on ludicrously small screens max-height: 80vh; - overflow: auto; - // Offsets the padding of the dialog. - // TODO(mmalerba): Remove when we switch away from using dialog. - margin: -24px; + // Allows for the screen reader close button to be seen in touch UI mode. + position: relative; + + // Prevents the content from jumping around on Windows while the animation is running. + overflow: visible; .mat-datepicker-content-container { min-height: $touch-min-height; diff --git a/src/material/datepicker/datepicker-module.ts b/src/material/datepicker/datepicker-module.ts index 9a863ab2e12d..5ce8072cbbda 100644 --- a/src/material/datepicker/datepicker-module.ts +++ b/src/material/datepicker/datepicker-module.ts @@ -12,7 +12,6 @@ import {PortalModule} from '@angular/cdk/portal'; import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; -import {MatDialogModule} from '@angular/material/dialog'; import {CdkScrollableModule} from '@angular/cdk/scrolling'; import {MatCommonModule} from '@angular/material/core'; import {MatCalendar, MatCalendarHeader} from './calendar'; @@ -38,7 +37,6 @@ import {MatDatepickerActions, MatDatepickerApply, MatDatepickerCancel} from './d imports: [ CommonModule, MatButtonModule, - MatDialogModule, OverlayModule, A11yModule, PortalModule, diff --git a/src/material/datepicker/datepicker.spec.ts b/src/material/datepicker/datepicker.spec.ts index 6714e3d58346..2bc7dd4c2edc 100644 --- a/src/material/datepicker/datepicker.spec.ts +++ b/src/material/datepicker/datepicker.spec.ts @@ -30,7 +30,6 @@ import {By} from '@angular/platform-browser'; import {_supportsShadowDom} from '@angular/cdk/platform'; import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {MAT_DIALOG_DEFAULT_OPTIONS, MatDialogConfig} from '@angular/material/dialog'; import {Subject} from 'rxjs'; import {MatInputModule} from '../input/index'; import {MatDatepicker} from './datepicker'; @@ -122,13 +121,12 @@ describe('MatDatepicker', () => { testComponent.touch = true; fixture.detectChanges(); - expect(document.querySelector('.mat-datepicker-dialog mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); testComponent.datepicker.open(); fixture.detectChanges(); - expect(document.querySelector('.mat-datepicker-dialog mat-dialog-container')) - .not.toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); }); it('should not be able to open more than one dialog', fakeAsync(() => { @@ -172,13 +170,13 @@ describe('MatDatepicker', () => { fixture.detectChanges(); expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); testComponent.datepicker.open(); fixture.detectChanges(); expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); }); it('disabled datepicker input should open the calendar if datepicker is enabled', () => { @@ -258,13 +256,13 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - expect(document.querySelector('mat-dialog-container')).not.toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); testComponent.datepicker.close(); fixture.detectChanges(); flush(); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); })); it('setting selected via click should update input and close calendar', fakeAsync(() => { @@ -275,7 +273,7 @@ describe('MatDatepicker', () => { fixture.detectChanges(); flush(); - expect(document.querySelector('mat-dialog-container')).not.toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1)); let cells = document.querySelectorAll('.mat-calendar-body-cell'); @@ -283,7 +281,7 @@ describe('MatDatepicker', () => { fixture.detectChanges(); flush(); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); })); @@ -296,7 +294,7 @@ describe('MatDatepicker', () => { fixture.detectChanges(); flush(); - expect(document.querySelector('mat-dialog-container')).not.toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1)); let calendarBodyEl = document.querySelector('.mat-calendar-body') as HTMLElement; @@ -308,7 +306,7 @@ describe('MatDatepicker', () => { fixture.detectChanges(); flush(); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); })); @@ -332,7 +330,7 @@ describe('MatDatepicker', () => { } expect(spy).toHaveBeenCalledTimes(1); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); selectedSubscription.unsubscribe(); })); @@ -354,7 +352,7 @@ describe('MatDatepicker', () => { fixture.whenStable().then(() => { expect(spy).not.toHaveBeenCalled(); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1)); selectedSubscription.unsubscribe(); }); @@ -1067,13 +1065,13 @@ describe('MatDatepicker', () => { }); it('should open calendar when toggle clicked', () => { - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); let toggle = fixture.debugElement.query(By.css('button'))!; dispatchMouseEvent(toggle.nativeElement, 'click'); fixture.detectChanges(); - expect(document.querySelector('mat-dialog-container')).not.toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); }); it('should not open calendar when toggle clicked if datepicker is disabled', () => { @@ -1082,12 +1080,12 @@ describe('MatDatepicker', () => { const toggle = fixture.debugElement.query(By.css('button'))!.nativeElement; expect(toggle.hasAttribute('disabled')).toBe(true); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); dispatchMouseEvent(toggle, 'click'); fixture.detectChanges(); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); }); it('should not open calendar when toggle clicked if input is disabled', () => { @@ -1098,12 +1096,12 @@ describe('MatDatepicker', () => { const toggle = fixture.debugElement.query(By.css('button'))!.nativeElement; expect(toggle.hasAttribute('disabled')).toBe(true); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); dispatchMouseEvent(toggle, 'click'); fixture.detectChanges(); - expect(document.querySelector('mat-dialog-container')).toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); }); it('should set the `button` type on the trigger to prevent form submissions', () => { @@ -1550,7 +1548,7 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - expect(document.querySelector('mat-dialog-container')).not.toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); let cells = document.querySelectorAll('.mat-calendar-body-cell'); expect(cells[0].classList).toContain('mat-calendar-body-disabled'); @@ -1661,7 +1659,7 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - expect(document.querySelector('mat-dialog-container')).not.toBeNull(); + expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); const cells = document.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cells[0], 'click'); @@ -2099,24 +2097,6 @@ describe('MatDatepicker', () => { })); }); - it('should not pick up values from the global dialog config', () => { - const fixture = createComponent(StandardDatepicker, [MatNativeDateModule], [{ - provide: MAT_DIALOG_DEFAULT_OPTIONS, - useValue: { - minWidth: '1337px', - hasBackdrop: false - } as MatDialogConfig - }]); - fixture.componentInstance.touch = true; - fixture.detectChanges(); - fixture.componentInstance.datepicker.open(); - fixture.detectChanges(); - - const overlay = document.querySelector('.cdk-overlay-pane') as HTMLElement; - expect(document.querySelector('.cdk-overlay-backdrop')).toBeTruthy(); - expect(overlay.style.minWidth).toBeFalsy(); - }); - it('should not trigger validators if new date object for same date is set for `min`', () => { const fixture = createComponent(DatepickerInputWithCustomValidator, [MatNativeDateModule], undefined, undefined, [CustomValidator]); @@ -2225,9 +2205,8 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - const datepickerContent = testComponent.datepicker['_dialogRef']!!.componentInstance; const actualClasses = - datepickerContent._elementRef.nativeElement.querySelector('.mat-calendar').classList; + document.querySelector('.mat-datepicker-content .mat-calendar')!.classList; expect(actualClasses.contains('foo')).toBe(true); expect(actualClasses.contains('bar')).toBe(true); }); diff --git a/tools/public_api_guard/material/datepicker.d.ts b/tools/public_api_guard/material/datepicker.d.ts index 852bd5865761..1c1c6ee59dcb 100644 --- a/tools/public_api_guard/material/datepicker.d.ts +++ b/tools/public_api_guard/material/datepicker.d.ts @@ -215,7 +215,7 @@ export declare class MatDatepickerCancel { export declare class MatDatepickerContent> extends _MatDatepickerContentMixinBase implements OnInit, AfterViewInit, OnDestroy, CanColor { _actionsPortal: TemplatePortal | null; readonly _animationDone: Subject; - _animationState: 'enter' | 'void'; + _animationState: 'enter-dropdown' | 'enter-dialog' | 'void'; _calendar: MatCalendar; _closeButtonFocused: boolean; _closeButtonText: string; @@ -292,7 +292,7 @@ export declare class MatDatepickerIntl { export declare class MatDatepickerModule { static ɵfac: i0.ɵɵFactoryDeclaration; static ɵinj: i0.ɵɵInjectorDeclaration; - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } export declare class MatDatepickerToggle implements AfterContentInit, OnChanges, OnDestroy {