Skip to content

Commit

Permalink
reverse snack-bar => legacy-snack-bar dep
Browse files Browse the repository at this point in the history
  • Loading branch information
mmalerba committed Aug 15, 2022
1 parent 0f4deef commit 76cc2f8
Show file tree
Hide file tree
Showing 22 changed files with 850 additions and 866 deletions.
12 changes: 6 additions & 6 deletions src/dev-app/mdc-snack-bar/mdc-snack-bar-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {Directionality} from '@angular/cdk/bidi';
import {Component, TemplateRef, ViewChild, ViewEncapsulation} from '@angular/core';
import {
MatSnackBar,
MatLegacySnackBarConfig,
MatLegacySnackBarHorizontalPosition,
MatLegacySnackBarVerticalPosition,
MatSnackBarConfig,
MatSnackBarHorizontalPosition,
MatSnackBarVerticalPosition,
MatSnackBarModule,
} from '@angular/material/snack-bar';
import {CommonModule} from '@angular/common';
Expand Down Expand Up @@ -46,8 +46,8 @@ export class MdcSnackBarDemo {
setAutoHide = true;
autoHide = 10000;
addExtraClass = false;
horizontalPosition: MatLegacySnackBarHorizontalPosition = 'center';
verticalPosition: MatLegacySnackBarVerticalPosition = 'bottom';
horizontalPosition: MatSnackBarHorizontalPosition = 'center';
verticalPosition: MatSnackBarVerticalPosition = 'bottom';

constructor(public snackBar: MatSnackBar, private _dir: Directionality) {}

Expand All @@ -62,7 +62,7 @@ export class MdcSnackBarDemo {
}

private _createConfig() {
const config = new MatLegacySnackBarConfig();
const config = new MatSnackBarConfig();
config.verticalPosition = this.verticalPosition;
config.horizontalPosition = this.horizontalPosition;
config.duration = this.setAutoHide ? this.autoHide : 0;
Expand Down
1 change: 1 addition & 0 deletions src/material/legacy-snack-bar/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ ng_module(
"//src/cdk/portal",
"//src/material/core",
"//src/material/legacy-button",
"//src/material/snack-bar",
"@npm//@angular/animations",
"@npm//@angular/common",
"@npm//@angular/core",
Expand Down
17 changes: 14 additions & 3 deletions src/material/legacy-snack-bar/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@
export * from './snack-bar-module';
export * from './snack-bar';
export * from './snack-bar-container';
export * from './snack-bar-config';
export * from './snack-bar-ref';
export * from './simple-snack-bar';
export * from './snack-bar-animations';
export {
MAT_SNACK_BAR_DATA as MAT_LEGACY_SNACK_BAR_DATA,
MatSnackBarHorizontalPosition as MatLegacySnackBarHorizontalPosition,
MatSnackBarVerticalPosition as MatLegacySnackBarVerticalPosition,
MatSnackBarConfig as MatLegacySnackBarConfig,
MatSnackBarDismiss as MatLegacySnackBarDismiss,
MatSnackBarRef as MatLegacySnackBarRef,
MAT_SNACK_BAR_DEFAULT_OPTIONS as MAT_LEGACY_SNACK_BAR_DEFAULT_OPTIONS,
MAT_SNACK_BAR_DEFAULT_OPTIONS_FACTORY as MAT_LEGACY_SNACK_BAR_DEFAULT_OPTIONS_FACTORY,
TextOnlySnackBar as LegacyTextOnlySnackBar,
matSnackBarAnimations as matLegacySnackBarAnimations,
_MatSnackBarContainerBase as _MatLegacySnackBarContainerBase,
_MatSnackBarBase as _MatLegacySnackBarBase,
} from '@angular/material/snack-bar';
19 changes: 4 additions & 15 deletions src/material/legacy-snack-bar/simple-snack-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,7 @@
*/

import {ChangeDetectionStrategy, Component, Inject, ViewEncapsulation} from '@angular/core';
import {MAT_LEGACY_SNACK_BAR_DATA} from './snack-bar-config';
import {MatLegacySnackBarRef} from './snack-bar-ref';

/**
* Interface for a simple snack bar component that has a message and a single action.
*/
export interface LegacyTextOnlySnackBar {
data: {message: string; action: string};
snackBarRef: MatLegacySnackBarRef<LegacyTextOnlySnackBar>;
action: () => void;
hasAction: boolean;
}
import {TextOnlySnackBar, MatSnackBarRef, MAT_SNACK_BAR_DATA} from '@angular/material/snack-bar';

/**
* A component used to open as the default snack bar, matching material spec.
Expand All @@ -34,13 +23,13 @@ export interface LegacyTextOnlySnackBar {
'class': 'mat-simple-snackbar',
},
})
export class LegacySimpleSnackBar implements LegacyTextOnlySnackBar {
export class LegacySimpleSnackBar implements TextOnlySnackBar {
/** Data that was injected into the snack bar. */
data: {message: string; action: string};

constructor(
public snackBarRef: MatLegacySnackBarRef<LegacySimpleSnackBar>,
@Inject(MAT_LEGACY_SNACK_BAR_DATA) data: any,
public snackBarRef: MatSnackBarRef<LegacySimpleSnackBar>,
@Inject(MAT_SNACK_BAR_DATA) data: any,
) {
this.data = data;
}
Expand Down
267 changes: 4 additions & 263 deletions src/material/legacy-snack-bar/snack-bar-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,267 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

import {AnimationEvent} from '@angular/animations';
import {AriaLivePoliteness} from '@angular/cdk/a11y';
import {Platform} from '@angular/cdk/platform';
import {
BasePortalOutlet,
CdkPortalOutlet,
ComponentPortal,
TemplatePortal,
DomPortal,
} from '@angular/cdk/portal';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ComponentRef,
Directive,
ElementRef,
EmbeddedViewRef,
NgZone,
OnDestroy,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {take} from 'rxjs/operators';
import {matLegacySnackBarAnimations} from './snack-bar-animations';
import {MatLegacySnackBarConfig} from './snack-bar-config';

/**
* Base class for snack bar containers.
* @docs-private
*/
@Directive()
export abstract class _MatLegacySnackBarContainerBase
extends BasePortalOutlet
implements OnDestroy
{
/** The number of milliseconds to wait before announcing the snack bar's content. */
private readonly _announceDelay: number = 150;

/** The timeout for announcing the snack bar's content. */
private _announceTimeoutId: number;

/** Whether the component has been destroyed. */
private _destroyed = false;

/** The portal outlet inside of this container into which the snack bar content will be loaded. */
@ViewChild(CdkPortalOutlet, {static: true}) _portalOutlet: CdkPortalOutlet;

/** Subject for notifying that the snack bar has announced to screen readers. */
readonly _onAnnounce: Subject<void> = new Subject();

/** Subject for notifying that the snack bar has exited from view. */
readonly _onExit: Subject<void> = new Subject();

/** Subject for notifying that the snack bar has finished entering the view. */
readonly _onEnter: Subject<void> = new Subject();

/** The state of the snack bar animations. */
_animationState = 'void';

/** aria-live value for the live region. */
_live: AriaLivePoliteness;

/**
* Role of the live region. This is only for Firefox as there is a known issue where Firefox +
* JAWS does not read out aria-live message.
*/
_role?: 'status' | 'alert';

constructor(
private _ngZone: NgZone,
protected _elementRef: ElementRef<HTMLElement>,
private _changeDetectorRef: ChangeDetectorRef,
private _platform: Platform,
/** The snack bar configuration. */
public snackBarConfig: MatLegacySnackBarConfig,
) {
super();

// Use aria-live rather than a live role like 'alert' or 'status'
// because NVDA and JAWS have show inconsistent behavior with live roles.
if (snackBarConfig.politeness === 'assertive' && !snackBarConfig.announcementMessage) {
this._live = 'assertive';
} else if (snackBarConfig.politeness === 'off') {
this._live = 'off';
} else {
this._live = 'polite';
}

// Only set role for Firefox. Set role based on aria-live because setting role="alert" implies
// aria-live="assertive" which may cause issues if aria-live is set to "polite" above.
if (this._platform.FIREFOX) {
if (this._live === 'polite') {
this._role = 'status';
}
if (this._live === 'assertive') {
this._role = 'alert';
}
}
}

/** Attach a component portal as content to this snack bar container. */
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
this._assertNotAttached();
const result = this._portalOutlet.attachComponentPortal(portal);
this._afterPortalAttached();
return result;
}

/** Attach a template portal as content to this snack bar container. */
attachTemplatePortal<C>(portal: TemplatePortal<C>): EmbeddedViewRef<C> {
this._assertNotAttached();
const result = this._portalOutlet.attachTemplatePortal(portal);
this._afterPortalAttached();
return result;
}

/**
* Attaches a DOM portal to the snack bar container.
* @deprecated To be turned into a method.
* @breaking-change 10.0.0
*/
override attachDomPortal = (portal: DomPortal) => {
this._assertNotAttached();
const result = this._portalOutlet.attachDomPortal(portal);
this._afterPortalAttached();
return result;
};

/** Handle end of animations, updating the state of the snackbar. */
onAnimationEnd(event: AnimationEvent) {
const {fromState, toState} = event;

if ((toState === 'void' && fromState !== 'void') || toState === 'hidden') {
this._completeExit();
}

if (toState === 'visible') {
// Note: we shouldn't use `this` inside the zone callback,
// because it can cause a memory leak.
const onEnter = this._onEnter;

this._ngZone.run(() => {
onEnter.next();
onEnter.complete();
});
}
}

/** Begin animation of snack bar entrance into view. */
enter(): void {
if (!this._destroyed) {
this._animationState = 'visible';
this._changeDetectorRef.detectChanges();
this._screenReaderAnnounce();
}
}

/** Begin animation of the snack bar exiting from view. */
exit(): Observable<void> {
// It's common for snack bars to be opened by random outside calls like HTTP requests or
// errors. Run inside the NgZone to ensure that it functions correctly.
this._ngZone.run(() => {
// Note: this one transitions to `hidden`, rather than `void`, in order to handle the case
// where multiple snack bars are opened in quick succession (e.g. two consecutive calls to
// `MatSnackBar.open`).
this._animationState = 'hidden';

// Mark this element with an 'exit' attribute to indicate that the snackbar has
// been dismissed and will soon be removed from the DOM. This is used by the snackbar
// test harness.
this._elementRef.nativeElement.setAttribute('mat-exit', '');

// If the snack bar hasn't been announced by the time it exits it wouldn't have been open
// long enough to visually read it either, so clear the timeout for announcing.
clearTimeout(this._announceTimeoutId);
});

return this._onExit;
}

/** Makes sure the exit callbacks have been invoked when the element is destroyed. */
ngOnDestroy() {
this._destroyed = true;
this._completeExit();
}

/**
* Waits for the zone to settle before removing the element. Helps prevent
* errors where we end up removing an element which is in the middle of an animation.
*/
private _completeExit() {
this._ngZone.onMicrotaskEmpty.pipe(take(1)).subscribe(() => {
this._ngZone.run(() => {
this._onExit.next();
this._onExit.complete();
});
});
}

/**
* Called after the portal contents have been attached. Can be
* used to modify the DOM once it's guaranteed to be in place.
*/
protected _afterPortalAttached() {
const element: HTMLElement = this._elementRef.nativeElement;
const panelClasses = this.snackBarConfig.panelClass;

if (panelClasses) {
if (Array.isArray(panelClasses)) {
// Note that we can't use a spread here, because IE doesn't support multiple arguments.
panelClasses.forEach(cssClass => element.classList.add(cssClass));
} else {
element.classList.add(panelClasses);
}
}
}

/** Asserts that no content is already attached to the container. */
private _assertNotAttached() {
if (this._portalOutlet.hasAttached() && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw Error('Attempting to attach snack bar content after content is already attached');
}
}

/**
* Starts a timeout to move the snack bar content to the live region so screen readers will
* announce it.
*/
private _screenReaderAnnounce() {
if (!this._announceTimeoutId) {
this._ngZone.runOutsideAngular(() => {
this._announceTimeoutId = setTimeout(() => {
const inertElement = this._elementRef.nativeElement.querySelector('[aria-hidden]');
const liveElement = this._elementRef.nativeElement.querySelector('[aria-live]');

if (inertElement && liveElement) {
// If an element in the snack bar content is focused before being moved
// track it and restore focus after moving to the live region.
let focusedElement: HTMLElement | null = null;
if (
this._platform.isBrowser &&
document.activeElement instanceof HTMLElement &&
inertElement.contains(document.activeElement)
) {
focusedElement = document.activeElement;
}

inertElement.removeAttribute('aria-hidden');
liveElement.appendChild(inertElement);
focusedElement?.focus();

this._onAnnounce.next();
this._onAnnounce.complete();
}
}, this._announceDelay);
});
}
}
}
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
import {_MatSnackBarContainerBase, matSnackBarAnimations} from '@angular/material/snack-bar';

/**
* Internal component that wraps user-provided snack bar content.
Expand All @@ -282,14 +23,14 @@ export abstract class _MatLegacySnackBarContainerBase
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.None,
animations: [matLegacySnackBarAnimations.snackBarState],
animations: [matSnackBarAnimations.snackBarState],
host: {
'class': 'mat-snack-bar-container',
'[@state]': '_animationState',
'(@state.done)': 'onAnimationEnd($event)',
},
})
export class MatLegacySnackBarContainer extends _MatLegacySnackBarContainerBase {
export class MatLegacySnackBarContainer extends _MatSnackBarContainerBase {
protected override _afterPortalAttached() {
super._afterPortalAttached();

Expand Down

0 comments on commit 76cc2f8

Please sign in to comment.