@angular/cdk
Version:
Angular Material Component Development Kit
1,017 lines (1,004 loc) • 158 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib'), require('@angular/cdk/scrolling'), require('@angular/common'), require('@angular/core'), require('@angular/cdk/coercion'), require('@angular/cdk/bidi'), require('@angular/cdk/portal'), require('@angular/cdk/platform'), require('rxjs'), require('rxjs/operators'), require('@angular/cdk/keycodes')) :
typeof define === 'function' && define.amd ? define('@angular/cdk/overlay', ['exports', 'tslib', '@angular/cdk/scrolling', '@angular/common', '@angular/core', '@angular/cdk/coercion', '@angular/cdk/bidi', '@angular/cdk/portal', '@angular/cdk/platform', 'rxjs', 'rxjs/operators', '@angular/cdk/keycodes'], factory) :
(global = global || self, factory((global.ng = global.ng || {}, global.ng.cdk = global.ng.cdk || {}, global.ng.cdk.overlay = {}), global.tslib, global.ng.cdk.scrolling, global.ng.common, global.ng.core, global.ng.cdk.coercion, global.ng.cdk.bidi, global.ng.cdk.portal, global.ng.cdk.platform, global.rxjs, global.rxjs.operators, global.ng.cdk.keycodes));
}(this, (function (exports, tslib, i1, i1$1, i0, coercion, bidi, portal, i2, rxjs, operators, keycodes) { 'use strict';
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Strategy that will prevent the user from scrolling while the overlay is visible.
*/
var BlockScrollStrategy = /** @class */ (function () {
function BlockScrollStrategy(_viewportRuler, document) {
this._viewportRuler = _viewportRuler;
this._previousHTMLStyles = { top: '', left: '' };
this._isEnabled = false;
this._document = document;
}
/** Attaches this scroll strategy to an overlay. */
BlockScrollStrategy.prototype.attach = function () { };
/** Blocks page-level scroll while the attached overlay is open. */
BlockScrollStrategy.prototype.enable = function () {
if (this._canBeEnabled()) {
var root = this._document.documentElement;
this._previousScrollPosition = this._viewportRuler.getViewportScrollPosition();
// Cache the previous inline styles in case the user had set them.
this._previousHTMLStyles.left = root.style.left || '';
this._previousHTMLStyles.top = root.style.top || '';
// Note: we're using the `html` node, instead of the `body`, because the `body` may
// have the user agent margin, whereas the `html` is guaranteed not to have one.
root.style.left = coercion.coerceCssPixelValue(-this._previousScrollPosition.left);
root.style.top = coercion.coerceCssPixelValue(-this._previousScrollPosition.top);
root.classList.add('cdk-global-scrollblock');
this._isEnabled = true;
}
};
/** Unblocks page-level scroll while the attached overlay is open. */
BlockScrollStrategy.prototype.disable = function () {
if (this._isEnabled) {
var html = this._document.documentElement;
var body = this._document.body;
var htmlStyle = html.style;
var bodyStyle = body.style;
var previousHtmlScrollBehavior = htmlStyle.scrollBehavior || '';
var previousBodyScrollBehavior = bodyStyle.scrollBehavior || '';
this._isEnabled = false;
htmlStyle.left = this._previousHTMLStyles.left;
htmlStyle.top = this._previousHTMLStyles.top;
html.classList.remove('cdk-global-scrollblock');
// Disable user-defined smooth scrolling temporarily while we restore the scroll position.
// See https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior
htmlStyle.scrollBehavior = bodyStyle.scrollBehavior = 'auto';
window.scroll(this._previousScrollPosition.left, this._previousScrollPosition.top);
htmlStyle.scrollBehavior = previousHtmlScrollBehavior;
bodyStyle.scrollBehavior = previousBodyScrollBehavior;
}
};
BlockScrollStrategy.prototype._canBeEnabled = function () {
// Since the scroll strategies can't be singletons, we have to use a global CSS class
// (`cdk-global-scrollblock`) to make sure that we don't try to disable global
// scrolling multiple times.
var html = this._document.documentElement;
if (html.classList.contains('cdk-global-scrollblock') || this._isEnabled) {
return false;
}
var body = this._document.body;
var viewport = this._viewportRuler.getViewportSize();
return body.scrollHeight > viewport.height || body.scrollWidth > viewport.width;
};
return BlockScrollStrategy;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Returns an error to be thrown when attempting to attach an already-attached scroll strategy.
*/
function getMatScrollStrategyAlreadyAttachedError() {
return Error("Scroll strategy has already been attached.");
}
/**
* Strategy that will close the overlay as soon as the user starts scrolling.
*/
var CloseScrollStrategy = /** @class */ (function () {
function CloseScrollStrategy(_scrollDispatcher, _ngZone, _viewportRuler, _config) {
var _this = this;
this._scrollDispatcher = _scrollDispatcher;
this._ngZone = _ngZone;
this._viewportRuler = _viewportRuler;
this._config = _config;
this._scrollSubscription = null;
/** Detaches the overlay ref and disables the scroll strategy. */
this._detach = function () {
_this.disable();
if (_this._overlayRef.hasAttached()) {
_this._ngZone.run(function () { return _this._overlayRef.detach(); });
}
};
}
/** Attaches this scroll strategy to an overlay. */
CloseScrollStrategy.prototype.attach = function (overlayRef) {
if (this._overlayRef) {
throw getMatScrollStrategyAlreadyAttachedError();
}
this._overlayRef = overlayRef;
};
/** Enables the closing of the attached overlay on scroll. */
CloseScrollStrategy.prototype.enable = function () {
var _this = this;
if (this._scrollSubscription) {
return;
}
var stream = this._scrollDispatcher.scrolled(0);
if (this._config && this._config.threshold && this._config.threshold > 1) {
this._initialScrollPosition = this._viewportRuler.getViewportScrollPosition().top;
this._scrollSubscription = stream.subscribe(function () {
var scrollPosition = _this._viewportRuler.getViewportScrollPosition().top;
if (Math.abs(scrollPosition - _this._initialScrollPosition) > _this._config.threshold) {
_this._detach();
}
else {
_this._overlayRef.updatePosition();
}
});
}
else {
this._scrollSubscription = stream.subscribe(this._detach);
}
};
/** Disables the closing the attached overlay on scroll. */
CloseScrollStrategy.prototype.disable = function () {
if (this._scrollSubscription) {
this._scrollSubscription.unsubscribe();
this._scrollSubscription = null;
}
};
CloseScrollStrategy.prototype.detach = function () {
this.disable();
this._overlayRef = null;
};
return CloseScrollStrategy;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/** Scroll strategy that doesn't do anything. */
var NoopScrollStrategy = /** @class */ (function () {
function NoopScrollStrategy() {
}
/** Does nothing, as this scroll strategy is a no-op. */
NoopScrollStrategy.prototype.enable = function () { };
/** Does nothing, as this scroll strategy is a no-op. */
NoopScrollStrategy.prototype.disable = function () { };
/** Does nothing, as this scroll strategy is a no-op. */
NoopScrollStrategy.prototype.attach = function () { };
return NoopScrollStrategy;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// TODO(jelbourn): move this to live with the rest of the scrolling code
// TODO(jelbourn): someday replace this with IntersectionObservers
/**
* Gets whether an element is scrolled outside of view by any of its parent scrolling containers.
* @param element Dimensions of the element (from getBoundingClientRect)
* @param scrollContainers Dimensions of element's scrolling containers (from getBoundingClientRect)
* @returns Whether the element is scrolled out of view
* @docs-private
*/
function isElementScrolledOutsideView(element, scrollContainers) {
return scrollContainers.some(function (containerBounds) {
var outsideAbove = element.bottom < containerBounds.top;
var outsideBelow = element.top > containerBounds.bottom;
var outsideLeft = element.right < containerBounds.left;
var outsideRight = element.left > containerBounds.right;
return outsideAbove || outsideBelow || outsideLeft || outsideRight;
});
}
/**
* Gets whether an element is clipped by any of its scrolling containers.
* @param element Dimensions of the element (from getBoundingClientRect)
* @param scrollContainers Dimensions of element's scrolling containers (from getBoundingClientRect)
* @returns Whether the element is clipped
* @docs-private
*/
function isElementClippedByScrolling(element, scrollContainers) {
return scrollContainers.some(function (scrollContainerRect) {
var clippedAbove = element.top < scrollContainerRect.top;
var clippedBelow = element.bottom > scrollContainerRect.bottom;
var clippedLeft = element.left < scrollContainerRect.left;
var clippedRight = element.right > scrollContainerRect.right;
return clippedAbove || clippedBelow || clippedLeft || clippedRight;
});
}
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Strategy that will update the element position as the user is scrolling.
*/
var RepositionScrollStrategy = /** @class */ (function () {
function RepositionScrollStrategy(_scrollDispatcher, _viewportRuler, _ngZone, _config) {
this._scrollDispatcher = _scrollDispatcher;
this._viewportRuler = _viewportRuler;
this._ngZone = _ngZone;
this._config = _config;
this._scrollSubscription = null;
}
/** Attaches this scroll strategy to an overlay. */
RepositionScrollStrategy.prototype.attach = function (overlayRef) {
if (this._overlayRef) {
throw getMatScrollStrategyAlreadyAttachedError();
}
this._overlayRef = overlayRef;
};
/** Enables repositioning of the attached overlay on scroll. */
RepositionScrollStrategy.prototype.enable = function () {
var _this = this;
if (!this._scrollSubscription) {
var throttle = this._config ? this._config.scrollThrottle : 0;
this._scrollSubscription = this._scrollDispatcher.scrolled(throttle).subscribe(function () {
_this._overlayRef.updatePosition();
// TODO(crisbeto): make `close` on by default once all components can handle it.
if (_this._config && _this._config.autoClose) {
var overlayRect = _this._overlayRef.overlayElement.getBoundingClientRect();
var _a = _this._viewportRuler.getViewportSize(), width = _a.width, height = _a.height;
// TODO(crisbeto): include all ancestor scroll containers here once
// we have a way of exposing the trigger element to the scroll strategy.
var parentRects = [{ width: width, height: height, bottom: height, right: width, top: 0, left: 0 }];
if (isElementScrolledOutsideView(overlayRect, parentRects)) {
_this.disable();
_this._ngZone.run(function () { return _this._overlayRef.detach(); });
}
}
});
}
};
/** Disables repositioning of the attached overlay on scroll. */
RepositionScrollStrategy.prototype.disable = function () {
if (this._scrollSubscription) {
this._scrollSubscription.unsubscribe();
this._scrollSubscription = null;
}
};
RepositionScrollStrategy.prototype.detach = function () {
this.disable();
this._overlayRef = null;
};
return RepositionScrollStrategy;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Options for how an overlay will handle scrolling.
*
* Users can provide a custom value for `ScrollStrategyOptions` to replace the default
* behaviors. This class primarily acts as a factory for ScrollStrategy instances.
*/
var ScrollStrategyOptions = /** @class */ (function () {
function ScrollStrategyOptions(_scrollDispatcher, _viewportRuler, _ngZone, document) {
var _this = this;
this._scrollDispatcher = _scrollDispatcher;
this._viewportRuler = _viewportRuler;
this._ngZone = _ngZone;
/** Do nothing on scroll. */
this.noop = function () { return new NoopScrollStrategy(); };
/**
* Close the overlay as soon as the user scrolls.
* @param config Configuration to be used inside the scroll strategy.
*/
this.close = function (config) { return new CloseScrollStrategy(_this._scrollDispatcher, _this._ngZone, _this._viewportRuler, config); };
/** Block scrolling. */
this.block = function () { return new BlockScrollStrategy(_this._viewportRuler, _this._document); };
/**
* Update the overlay's position on scroll.
* @param config Configuration to be used inside the scroll strategy.
* Allows debouncing the reposition calls.
*/
this.reposition = function (config) { return new RepositionScrollStrategy(_this._scrollDispatcher, _this._viewportRuler, _this._ngZone, config); };
this._document = document;
}
ScrollStrategyOptions.decorators = [
{ type: i0.Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */
ScrollStrategyOptions.ctorParameters = function () { return [
{ type: i1.ScrollDispatcher },
{ type: i1.ViewportRuler },
{ type: i0.NgZone },
{ type: undefined, decorators: [{ type: i0.Inject, args: [i1$1.DOCUMENT,] }] }
]; };
ScrollStrategyOptions.ɵprov = i0.ɵɵdefineInjectable({ factory: function ScrollStrategyOptions_Factory() { return new ScrollStrategyOptions(i0.ɵɵinject(i1.ScrollDispatcher), i0.ɵɵinject(i1.ViewportRuler), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i1$1.DOCUMENT)); }, token: ScrollStrategyOptions, providedIn: "root" });
return ScrollStrategyOptions;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/** Initial configuration used when creating an overlay. */
var OverlayConfig = /** @class */ (function () {
function OverlayConfig(config) {
var e_1, _a;
/** Strategy to be used when handling scroll events while the overlay is open. */
this.scrollStrategy = new NoopScrollStrategy();
/** Custom class to add to the overlay pane. */
this.panelClass = '';
/** Whether the overlay has a backdrop. */
this.hasBackdrop = false;
/** Custom class to add to the backdrop */
this.backdropClass = 'cdk-overlay-dark-backdrop';
/**
* Whether the overlay should be disposed of when the user goes backwards/forwards in history.
* Note that this usually doesn't include clicking on links (unless the user is using
* the `HashLocationStrategy`).
*/
this.disposeOnNavigation = false;
if (config) {
// Use `Iterable` instead of `Array` because TypeScript, as of 3.6.3,
// loses the array generic type in the `for of`. But we *also* have to use `Array` because
// typescript won't iterate over an `Iterable` unless you compile with `--downlevelIteration`
var configKeys = Object.keys(config);
try {
for (var configKeys_1 = tslib.__values(configKeys), configKeys_1_1 = configKeys_1.next(); !configKeys_1_1.done; configKeys_1_1 = configKeys_1.next()) {
var key = configKeys_1_1.value;
if (config[key] !== undefined) {
// TypeScript, as of version 3.5, sees the left-hand-side of this expression
// as "I don't know *which* key this is, so the only valid value is the intersection
// of all the posible values." In this case, that happens to be `undefined`. TypeScript
// is not smart enough to see that the right-hand-side is actually an access of the same
// exact type with the same exact key, meaning that the value type must be identical.
// So we use `any` to work around this.
this[key] = config[key];
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (configKeys_1_1 && !configKeys_1_1.done && (_a = configKeys_1.return)) _a.call(configKeys_1);
}
finally { if (e_1) throw e_1.error; }
}
}
}
return OverlayConfig;
}());
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/** The points of the origin element and the overlay element to connect. */
var ConnectionPositionPair = /** @class */ (function () {
function ConnectionPositionPair(origin, overlay,
/** Offset along the X axis. */
offsetX,
/** Offset along the Y axis. */
offsetY,
/** Class(es) to be applied to the panel while this position is active. */
panelClass) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.panelClass = panelClass;
this.originX = origin.originX;
this.originY = origin.originY;
this.overlayX = overlay.overlayX;
this.overlayY = overlay.overlayY;
}
return ConnectionPositionPair;
}());
/**
* Set of properties regarding the position of the origin and overlay relative to the viewport
* with respect to the containing Scrollable elements.
*
* The overlay and origin are clipped if any part of their bounding client rectangle exceeds the
* bounds of any one of the strategy's Scrollable's bounding client rectangle.
*
* The overlay and origin are outside view if there is no overlap between their bounding client
* rectangle and any one of the strategy's Scrollable's bounding client rectangle.
*
* ----------- -----------
* | outside | | clipped |
* | view | --------------------------
* | | | | | |
* ---------- | ----------- |
* -------------------------- | |
* | | | Scrollable |
* | | | |
* | | --------------------------
* | Scrollable |
* | |
* --------------------------
*
* @docs-private
*/
var ScrollingVisibility = /** @class */ (function () {
function ScrollingVisibility() {
}
return ScrollingVisibility;
}());
/** The change event emitted by the strategy when a fallback position is used. */
var ConnectedOverlayPositionChange = /** @class */ (function () {
function ConnectedOverlayPositionChange(
/** The position used as a result of this change. */
connectionPair,
/** @docs-private */
scrollableViewProperties) {
this.connectionPair = connectionPair;
this.scrollableViewProperties = scrollableViewProperties;
}
/** @nocollapse */
ConnectedOverlayPositionChange.ctorParameters = function () { return [
{ type: ConnectionPositionPair },
{ type: ScrollingVisibility, decorators: [{ type: i0.Optional }] }
]; };
return ConnectedOverlayPositionChange;
}());
/**
* Validates whether a vertical position property matches the expected values.
* @param property Name of the property being validated.
* @param value Value of the property being validated.
* @docs-private
*/
function validateVerticalPosition(property, value) {
if (value !== 'top' && value !== 'bottom' && value !== 'center') {
throw Error("ConnectedPosition: Invalid " + property + " \"" + value + "\". " +
"Expected \"top\", \"bottom\" or \"center\".");
}
}
/**
* Validates whether a horizontal position property matches the expected values.
* @param property Name of the property being validated.
* @param value Value of the property being validated.
* @docs-private
*/
function validateHorizontalPosition(property, value) {
if (value !== 'start' && value !== 'end' && value !== 'center') {
throw Error("ConnectedPosition: Invalid " + property + " \"" + value + "\". " +
"Expected \"start\", \"end\" or \"center\".");
}
}
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Service for dispatching keyboard events that land on the body to appropriate overlay ref,
* if any. It maintains a list of attached overlays to determine best suited overlay based
* on event target and order of overlay opens.
*/
var OverlayKeyboardDispatcher = /** @class */ (function () {
function OverlayKeyboardDispatcher(document) {
var _this = this;
/** Currently attached overlays in the order they were attached. */
this._attachedOverlays = [];
/** Keyboard event listener that will be attached to the body. */
this._keydownListener = function (event) {
var overlays = _this._attachedOverlays;
for (var i = overlays.length - 1; i > -1; i--) {
// Dispatch the keydown event to the top overlay which has subscribers to its keydown events.
// We want to target the most recent overlay, rather than trying to match where the event came
// from, because some components might open an overlay, but keep focus on a trigger element
// (e.g. for select and autocomplete). We skip overlays without keydown event subscriptions,
// because we don't want overlays that don't handle keyboard events to block the ones below
// them that do.
if (overlays[i]._keydownEventSubscriptions > 0) {
overlays[i]._keydownEvents.next(event);
break;
}
}
};
this._document = document;
}
OverlayKeyboardDispatcher.prototype.ngOnDestroy = function () {
this._detach();
};
/** Add a new overlay to the list of attached overlay refs. */
OverlayKeyboardDispatcher.prototype.add = function (overlayRef) {
// Ensure that we don't get the same overlay multiple times.
this.remove(overlayRef);
// Lazily start dispatcher once first overlay is added
if (!this._isAttached) {
this._document.body.addEventListener('keydown', this._keydownListener);
this._isAttached = true;
}
this._attachedOverlays.push(overlayRef);
};
/** Remove an overlay from the list of attached overlay refs. */
OverlayKeyboardDispatcher.prototype.remove = function (overlayRef) {
var index = this._attachedOverlays.indexOf(overlayRef);
if (index > -1) {
this._attachedOverlays.splice(index, 1);
}
// Remove the global listener once there are no more overlays.
if (this._attachedOverlays.length === 0) {
this._detach();
}
};
/** Detaches the global keyboard event listener. */
OverlayKeyboardDispatcher.prototype._detach = function () {
if (this._isAttached) {
this._document.body.removeEventListener('keydown', this._keydownListener);
this._isAttached = false;
}
};
OverlayKeyboardDispatcher.decorators = [
{ type: i0.Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */
OverlayKeyboardDispatcher.ctorParameters = function () { return [
{ type: undefined, decorators: [{ type: i0.Inject, args: [i1$1.DOCUMENT,] }] }
]; };
OverlayKeyboardDispatcher.ɵprov = i0.ɵɵdefineInjectable({ factory: function OverlayKeyboardDispatcher_Factory() { return new OverlayKeyboardDispatcher(i0.ɵɵinject(i1$1.DOCUMENT)); }, token: OverlayKeyboardDispatcher, providedIn: "root" });
return OverlayKeyboardDispatcher;
}());
/** @docs-private @deprecated @breaking-change 8.0.0 */
function OVERLAY_KEYBOARD_DISPATCHER_PROVIDER_FACTORY(dispatcher, _document) {
return dispatcher || new OverlayKeyboardDispatcher(_document);
}
/** @docs-private @deprecated @breaking-change 8.0.0 */
var OVERLAY_KEYBOARD_DISPATCHER_PROVIDER = {
// If there is already an OverlayKeyboardDispatcher available, use that.
// Otherwise, provide a new one.
provide: OverlayKeyboardDispatcher,
deps: [
[new i0.Optional(), new i0.SkipSelf(), OverlayKeyboardDispatcher],
// Coerce to `InjectionToken` so that the `deps` match the "shape"
// of the type expected by Angular
i1$1.DOCUMENT
],
useFactory: OVERLAY_KEYBOARD_DISPATCHER_PROVIDER_FACTORY
};
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Whether we're in a testing environment.
* TODO(crisbeto): remove this once we have an overlay testing module.
*/
var isTestEnvironment = typeof window !== 'undefined' && !!window &&
!!(window.__karma__ || window.jasmine);
/** Container inside which all overlays will render. */
var OverlayContainer = /** @class */ (function () {
function OverlayContainer(document,
/**
* @deprecated `platform` parameter to become required.
* @breaking-change 10.0.0
*/
_platform) {
this._platform = _platform;
this._document = document;
}
OverlayContainer.prototype.ngOnDestroy = function () {
var container = this._containerElement;
if (container && container.parentNode) {
container.parentNode.removeChild(container);
}
};
/**
* This method returns the overlay container element. It will lazily
* create the element the first time it is called to facilitate using
* the container in non-browser environments.
* @returns the container element
*/
OverlayContainer.prototype.getContainerElement = function () {
if (!this._containerElement) {
this._createContainer();
}
return this._containerElement;
};
/**
* Create the overlay container element, which is simply a div
* with the 'cdk-overlay-container' class on the document body.
*/
OverlayContainer.prototype._createContainer = function () {
// @breaking-change 10.0.0 Remove null check for `_platform`.
var isBrowser = this._platform ? this._platform.isBrowser : typeof window !== 'undefined';
var containerClass = 'cdk-overlay-container';
if (isBrowser || isTestEnvironment) {
var oppositePlatformContainers = this._document.querySelectorAll("." + containerClass + "[platform=\"server\"], " +
("." + containerClass + "[platform=\"test\"]"));
// Remove any old containers from the opposite platform.
// This can happen when transitioning from the server to the client.
for (var i = 0; i < oppositePlatformContainers.length; i++) {
oppositePlatformContainers[i].parentNode.removeChild(oppositePlatformContainers[i]);
}
}
var container = this._document.createElement('div');
container.classList.add(containerClass);
// A long time ago we kept adding new overlay containers whenever a new app was instantiated,
// but at some point we added logic which clears the duplicate ones in order to avoid leaks.
// The new logic was a little too aggressive since it was breaking some legitimate use cases.
// To mitigate the problem we made it so that only containers from a different platform are
// cleared, but the side-effect was that people started depending on the overly-aggressive
// logic to clean up their tests for them. Until we can introduce an overlay-specific testing
// module which does the cleanup, we try to detect that we're in a test environment and we
// always clear the container. See #17006.
// TODO(crisbeto): remove the test environment check once we have an overlay testing module.
if (isTestEnvironment) {
container.setAttribute('platform', 'test');
}
else if (!isBrowser) {
container.setAttribute('platform', 'server');
}
this._document.body.appendChild(container);
this._containerElement = container;
};
OverlayContainer.decorators = [
{ type: i0.Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */
OverlayContainer.ctorParameters = function () { return [
{ type: undefined, decorators: [{ type: i0.Inject, args: [i1$1.DOCUMENT,] }] },
{ type: i2.Platform }
]; };
OverlayContainer.ɵprov = i0.ɵɵdefineInjectable({ factory: function OverlayContainer_Factory() { return new OverlayContainer(i0.ɵɵinject(i1$1.DOCUMENT), i0.ɵɵinject(i2.Platform)); }, token: OverlayContainer, providedIn: "root" });
return OverlayContainer;
}());
/** @docs-private @deprecated @breaking-change 8.0.0 */
function OVERLAY_CONTAINER_PROVIDER_FACTORY(parentContainer, _document) {
return parentContainer || new OverlayContainer(_document);
}
/** @docs-private @deprecated @breaking-change 8.0.0 */
var OVERLAY_CONTAINER_PROVIDER = {
// If there is already an OverlayContainer available, use that. Otherwise, provide a new one.
provide: OverlayContainer,
deps: [
[new i0.Optional(), new i0.SkipSelf(), OverlayContainer],
i1$1.DOCUMENT // We need to use the InjectionToken somewhere to keep TS happy
],
useFactory: OVERLAY_CONTAINER_PROVIDER_FACTORY
};
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Reference to an overlay that has been created with the Overlay service.
* Used to manipulate or dispose of said overlay.
*/
var OverlayRef = /** @class */ (function () {
function OverlayRef(_portalOutlet, _host, _pane, _config, _ngZone, _keyboardDispatcher, _document,
// @breaking-change 8.0.0 `_location` parameter to be made required.
_location) {
var _this = this;
this._portalOutlet = _portalOutlet;
this._host = _host;
this._pane = _pane;
this._config = _config;
this._ngZone = _ngZone;
this._keyboardDispatcher = _keyboardDispatcher;
this._document = _document;
this._location = _location;
this._backdropElement = null;
this._backdropClick = new rxjs.Subject();
this._attachments = new rxjs.Subject();
this._detachments = new rxjs.Subject();
this._locationChanges = rxjs.Subscription.EMPTY;
this._backdropClickHandler = function (event) { return _this._backdropClick.next(event); };
this._keydownEventsObservable = new rxjs.Observable(function (observer) {
var subscription = _this._keydownEvents.subscribe(observer);
_this._keydownEventSubscriptions++;
return function () {
subscription.unsubscribe();
_this._keydownEventSubscriptions--;
};
});
/** Stream of keydown events dispatched to this overlay. */
this._keydownEvents = new rxjs.Subject();
/** Amount of subscriptions to the keydown events. */
this._keydownEventSubscriptions = 0;
if (_config.scrollStrategy) {
this._scrollStrategy = _config.scrollStrategy;
this._scrollStrategy.attach(this);
}
this._positionStrategy = _config.positionStrategy;
}
Object.defineProperty(OverlayRef.prototype, "overlayElement", {
/** The overlay's HTML element */
get: function () {
return this._pane;
},
enumerable: true,
configurable: true
});
Object.defineProperty(OverlayRef.prototype, "backdropElement", {
/** The overlay's backdrop HTML element. */
get: function () {
return this._backdropElement;
},
enumerable: true,
configurable: true
});
Object.defineProperty(OverlayRef.prototype, "hostElement", {
/**
* Wrapper around the panel element. Can be used for advanced
* positioning where a wrapper with specific styling is
* required around the overlay pane.
*/
get: function () {
return this._host;
},
enumerable: true,
configurable: true
});
/**
* Attaches content, given via a Portal, to the overlay.
* If the overlay is configured to have a backdrop, it will be created.
*
* @param portal Portal instance to which to attach the overlay.
* @returns The portal attachment result.
*/
OverlayRef.prototype.attach = function (portal) {
var _this = this;
var attachResult = this._portalOutlet.attach(portal);
if (this._positionStrategy) {
this._positionStrategy.attach(this);
}
// Update the pane element with the given configuration.
if (!this._host.parentElement && this._previousHostParent) {
this._previousHostParent.appendChild(this._host);
}
this._updateStackingOrder();
this._updateElementSize();
this._updateElementDirection();
if (this._scrollStrategy) {
this._scrollStrategy.enable();
}
// Update the position once the zone is stable so that the overlay will be fully rendered
// before attempting to position it, as the position may depend on the size of the rendered
// content.
this._ngZone.onStable
.asObservable()
.pipe(operators.take(1))
.subscribe(function () {
// The overlay could've been detached before the zone has stabilized.
if (_this.hasAttached()) {
_this.updatePosition();
}
});
// Enable pointer events for the overlay pane element.
this._togglePointerEvents(true);
if (this._config.hasBackdrop) {
this._attachBackdrop();
}
if (this._config.panelClass) {
this._toggleClasses(this._pane, this._config.panelClass, true);
}
// Only emit the `attachments` event once all other setup is done.
this._attachments.next();
// Track this overlay by the keyboard dispatcher
this._keyboardDispatcher.add(this);
// @breaking-change 8.0.0 remove the null check for `_location`
// once the constructor parameter is made required.
if (this._config.disposeOnNavigation && this._location) {
this._locationChanges = this._location.subscribe(function () { return _this.dispose(); });
}
return attachResult;
};
/**
* Detaches an overlay from a portal.
* @returns The portal detachment result.
*/
OverlayRef.prototype.detach = function () {
if (!this.hasAttached()) {
return;
}
this.detachBackdrop();
// When the overlay is detached, the pane element should disable pointer events.
// This is necessary because otherwise the pane element will cover the page and disable
// pointer events therefore. Depends on the position strategy and the applied pane boundaries.
this._togglePointerEvents(false);
if (this._positionStrategy && this._positionStrategy.detach) {
this._positionStrategy.detach();
}
if (this._scrollStrategy) {
this._scrollStrategy.disable();
}
var detachmentResult = this._portalOutlet.detach();
// Only emit after everything is detached.
this._detachments.next();
// Remove this overlay from keyboard dispatcher tracking.
this._keyboardDispatcher.remove(this);
// Keeping the host element in the DOM can cause scroll jank, because it still gets
// rendered, even though it's transparent and unclickable which is why we remove it.
this._detachContentWhenStable();
// Stop listening for location changes.
this._locationChanges.unsubscribe();
return detachmentResult;
};
/** Cleans up the overlay from the DOM. */
OverlayRef.prototype.dispose = function () {
var isAttached = this.hasAttached();
if (this._positionStrategy) {
this._positionStrategy.dispose();
}
this._disposeScrollStrategy();
this.detachBackdrop();
this._locationChanges.unsubscribe();
this._keyboardDispatcher.remove(this);
this._portalOutlet.dispose();
this._attachments.complete();
this._backdropClick.complete();
this._keydownEvents.complete();
if (this._host && this._host.parentNode) {
this._host.parentNode.removeChild(this._host);
this._host = null;
}
this._previousHostParent = this._pane = null;
if (isAttached) {
this._detachments.next();
}
this._detachments.complete();
};
/** Whether the overlay has attached content. */
OverlayRef.prototype.hasAttached = function () {
return this._portalOutlet.hasAttached();
};
/** Gets an observable that emits when the backdrop has been clicked. */
OverlayRef.prototype.backdropClick = function () {
return this._backdropClick.asObservable();
};
/** Gets an observable that emits when the overlay has been attached. */
OverlayRef.prototype.attachments = function () {
return this._attachments.asObservable();
};
/** Gets an observable that emits when the overlay has been detached. */
OverlayRef.prototype.detachments = function () {
return this._detachments.asObservable();
};
/** Gets an observable of keydown events targeted to this overlay. */
OverlayRef.prototype.keydownEvents = function () {
return this._keydownEventsObservable;
};
/** Gets the current overlay configuration, which is immutable. */
OverlayRef.prototype.getConfig = function () {
return this._config;
};
/** Updates the position of the overlay based on the position strategy. */
OverlayRef.prototype.updatePosition = function () {
if (this._positionStrategy) {
this._positionStrategy.apply();
}
};
/** Switches to a new position strategy and updates the overlay position. */
OverlayRef.prototype.updatePositionStrategy = function (strategy) {
if (strategy === this._positionStrategy) {
return;
}
if (this._positionStrategy) {
this._positionStrategy.dispose();
}
this._positionStrategy = strategy;
if (this.hasAttached()) {
strategy.attach(this);
this.updatePosition();
}
};
/** Update the size properties of the overlay. */
OverlayRef.prototype.updateSize = function (sizeConfig) {
this._config = tslib.__assign(tslib.__assign({}, this._config), sizeConfig);
this._updateElementSize();
};
/** Sets the LTR/RTL direction for the overlay. */
OverlayRef.prototype.setDirection = function (dir) {
this._config = tslib.__assign(tslib.__assign({}, this._config), { direction: dir });
this._updateElementDirection();
};
/** Add a CSS class or an array of classes to the overlay pane. */
OverlayRef.prototype.addPanelClass = function (classes) {
if (this._pane) {
this._toggleClasses(this._pane, classes, true);
}
};
/** Remove a CSS class or an array of classes from the overlay pane. */
OverlayRef.prototype.removePanelClass = function (classes) {
if (this._pane) {
this._toggleClasses(this._pane, classes, false);
}
};
/**
* Returns the layout direction of the overlay panel.
*/
OverlayRef.prototype.getDirection = function () {
var direction = this._config.direction;
if (!direction) {
return 'ltr';
}
return typeof direction === 'string' ? direction : direction.value;
};
/** Switches to a new scroll strategy. */
OverlayRef.prototype.updateScrollStrategy = function (strategy) {
if (strategy === this._scrollStrategy) {
return;
}
this._disposeScrollStrategy();
this._scrollStrategy = strategy;
if (this.hasAttached()) {
strategy.attach(this);
strategy.enable();
}
};
/** Updates the text direction of the overlay panel. */
OverlayRef.prototype._updateElementDirection = function () {
this._host.setAttribute('dir', this.getDirection());
};
/** Updates the size of the overlay element based on the overlay config. */
OverlayRef.prototype._updateElementSize = function () {
if (!this._pane) {
return;
}
var style = this._pane.style;
style.width = coercion.coerceCssPixelValue(this._config.width);
style.height = coercion.coerceCssPixelValue(this._config.height);
style.minWidth = coercion.coerceCssPixelValue(this._config.minWidth);
style.minHeight = coercion.coerceCssPixelValue(this._config.minHeight);
style.maxWidth = coercion.coerceCssPixelValue(this._config.maxWidth);
style.maxHeight = coercion.coerceCssPixelValue(this._config.maxHeight);
};
/** Toggles the pointer events for the overlay pane element. */
OverlayRef.prototype._togglePointerEvents = function (enablePointer) {
this._pane.style.pointerEvents = enablePointer ? 'auto' : 'none';
};
/** Attaches a backdrop for this overlay. */
OverlayRef.prototype._attachBackdrop = function () {
var _this = this;
var showingClass = 'cdk-overlay-backdrop-showing';
this._backdropElement = this._document.createElement('div');
this._backdropElement.classList.add('cdk-overlay-backdrop');
if (this._config.backdropClass) {
this._toggleClasses(this._backdropElement, this._config.backdropClass, true);
}
// Insert the backdrop before the pane in the DOM order,
// in order to handle stacked overlays properly.
this._host.parentElement.insertBefore(this._backdropElement, this._host);
// Forward backdrop clicks such that the consumer of the overlay can perform whatever
// action desired when such a click occurs (usually closing the overlay).
this._backdropElement.addEventListener('click', this._backdropClickHandler);
// Add class to fade-in the backdrop after one frame.
if (typeof requestAnimationFrame !== 'undefined') {
this._ngZone.runOutsideAngular(function () {
requestAnimationFrame(function () {
if (_this._backdropElement) {
_this._backdropElement.classList.add(showingClass);
}
});
});
}
else {
this._backdropElement.classList.add(showingClass);
}
};
/**
* Updates the stacking order of the element, moving it to the top if necessary.
* This is required in cases where one overlay was detached, while another one,
* that should be behind it, was destroyed. The next time both of them are opened,
* the sta