import { ScrollDispatcher, ViewportRuler, ScrollingModule } from '@angular/cdk/scrolling'; export { CdkScrollable, ScrollDispatcher, ViewportRuler } from '@angular/cdk/scrolling'; import { DOCUMENT, Location } from '@angular/common'; import { Injectable, NgZone, Inject, ɵɵdefineInjectable, ɵɵinject, Optional, SkipSelf, ElementRef, ApplicationRef, ComponentFactoryResolver, Injector, InjectionToken, Directive, EventEmitter, TemplateRef, ViewContainerRef, Input, Output, NgModule } from '@angular/core'; import { coerceCssPixelValue, coerceArray, coerceBooleanProperty } from '@angular/cdk/coercion'; import { Directionality, BidiModule } from '@angular/cdk/bidi'; import { DomPortalOutlet, TemplatePortal, PortalModule } from '@angular/cdk/portal'; import { Platform } from '@angular/cdk/platform'; import { Subject, Subscription, Observable, merge } from 'rxjs'; import { take, takeUntil } from 'rxjs/operators'; import { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes'; /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/scroll/block-scroll-strategy.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Strategy that will prevent the user from scrolling while the overlay is visible. */ class BlockScrollStrategy { /** * @param {?} _viewportRuler * @param {?} document */ constructor(_viewportRuler, document) { this._viewportRuler = _viewportRuler; this._previousHTMLStyles = { top: '', left: '' }; this._isEnabled = false; this._document = document; } /** * Attaches this scroll strategy to an overlay. * @return {?} */ attach() { } /** * Blocks page-level scroll while the attached overlay is open. * @return {?} */ enable() { if (this._canBeEnabled()) { /** @type {?} */ const root = (/** @type {?} */ (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 = coerceCssPixelValue(-this._previousScrollPosition.left); root.style.top = coerceCssPixelValue(-this._previousScrollPosition.top); root.classList.add('cdk-global-scrollblock'); this._isEnabled = true; } } /** * Unblocks page-level scroll while the attached overlay is open. * @return {?} */ disable() { if (this._isEnabled) { /** @type {?} */ const html = (/** @type {?} */ (this._document.documentElement)); /** @type {?} */ const body = (/** @type {?} */ (this._document.body)); /** @type {?} */ const htmlStyle = (/** @type {?} */ (html.style)); /** @type {?} */ const bodyStyle = (/** @type {?} */ (body.style)); /** @type {?} */ const previousHtmlScrollBehavior = htmlStyle.scrollBehavior || ''; /** @type {?} */ const 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; } } /** * @private * @return {?} */ _canBeEnabled() { // 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. /** @type {?} */ const html = (/** @type {?} */ (this._document.documentElement)); if (html.classList.contains('cdk-global-scrollblock') || this._isEnabled) { return false; } /** @type {?} */ const body = this._document.body; /** @type {?} */ const viewport = this._viewportRuler.getViewportSize(); return body.scrollHeight > viewport.height || body.scrollWidth > viewport.width; } } if (false) { /** * @type {?} * @private */ BlockScrollStrategy.prototype._previousHTMLStyles; /** * @type {?} * @private */ BlockScrollStrategy.prototype._previousScrollPosition; /** * @type {?} * @private */ BlockScrollStrategy.prototype._isEnabled; /** * @type {?} * @private */ BlockScrollStrategy.prototype._document; /** * @type {?} * @private */ BlockScrollStrategy.prototype._viewportRuler; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/scroll/scroll-strategy.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @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 */ /** * Describes a strategy that will be used by an overlay to handle scroll events while it is open. * @record */ function ScrollStrategy() { } if (false) { /** * Enable this scroll strategy (called when the attached overlay is attached to a portal). * @type {?} */ ScrollStrategy.prototype.enable; /** * Disable this scroll strategy (called when the attached overlay is detached from a portal). * @type {?} */ ScrollStrategy.prototype.disable; /** * Attaches this `ScrollStrategy` to an overlay. * @type {?} */ ScrollStrategy.prototype.attach; /** * Detaches the scroll strategy from the current overlay. * @type {?|undefined} */ ScrollStrategy.prototype.detach; } /** * Returns an error to be thrown when attempting to attach an already-attached scroll strategy. * @return {?} */ function getMatScrollStrategyAlreadyAttachedError() { return Error(`Scroll strategy has already been attached.`); } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/scroll/close-scroll-strategy.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Config options for the CloseScrollStrategy. * @record */ function CloseScrollStrategyConfig() { } if (false) { /** * Amount of pixels the user has to scroll before the overlay is closed. * @type {?|undefined} */ CloseScrollStrategyConfig.prototype.threshold; } /** * Strategy that will close the overlay as soon as the user starts scrolling. */ class CloseScrollStrategy { /** * @param {?} _scrollDispatcher * @param {?} _ngZone * @param {?} _viewportRuler * @param {?=} _config */ constructor(_scrollDispatcher, _ngZone, _viewportRuler, _config) { 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 = (/** * @return {?} */ () => { this.disable(); if (this._overlayRef.hasAttached()) { this._ngZone.run((/** * @return {?} */ () => this._overlayRef.detach())); } }); } /** * Attaches this scroll strategy to an overlay. * @param {?} overlayRef * @return {?} */ attach(overlayRef) { if (this._overlayRef) { throw getMatScrollStrategyAlreadyAttachedError(); } this._overlayRef = overlayRef; } /** * Enables the closing of the attached overlay on scroll. * @return {?} */ enable() { if (this._scrollSubscription) { return; } /** @type {?} */ const 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((/** * @return {?} */ () => { /** @type {?} */ const scrollPosition = this._viewportRuler.getViewportScrollPosition().top; if (Math.abs(scrollPosition - this._initialScrollPosition) > (/** @type {?} */ ((/** @type {?} */ (this._config)).threshold))) { this._detach(); } else { this._overlayRef.updatePosition(); } })); } else { this._scrollSubscription = stream.subscribe(this._detach); } } /** * Disables the closing the attached overlay on scroll. * @return {?} */ disable() { if (this._scrollSubscription) { this._scrollSubscription.unsubscribe(); this._scrollSubscription = null; } } /** * @return {?} */ detach() { this.disable(); this._overlayRef = (/** @type {?} */ (null)); } } if (false) { /** * @type {?} * @private */ CloseScrollStrategy.prototype._scrollSubscription; /** * @type {?} * @private */ CloseScrollStrategy.prototype._overlayRef; /** * @type {?} * @private */ CloseScrollStrategy.prototype._initialScrollPosition; /** * Detaches the overlay ref and disables the scroll strategy. * @type {?} * @private */ CloseScrollStrategy.prototype._detach; /** * @type {?} * @private */ CloseScrollStrategy.prototype._scrollDispatcher; /** * @type {?} * @private */ CloseScrollStrategy.prototype._ngZone; /** * @type {?} * @private */ CloseScrollStrategy.prototype._viewportRuler; /** * @type {?} * @private */ CloseScrollStrategy.prototype._config; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/scroll/noop-scroll-strategy.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @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. */ class NoopScrollStrategy { /** * Does nothing, as this scroll strategy is a no-op. * @return {?} */ enable() { } /** * Does nothing, as this scroll strategy is a no-op. * @return {?} */ disable() { } /** * Does nothing, as this scroll strategy is a no-op. * @return {?} */ attach() { } } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/position/scroll-clip.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @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. * \@docs-private * @param {?} element Dimensions of the element (from getBoundingClientRect) * @param {?} scrollContainers Dimensions of element's scrolling containers (from getBoundingClientRect) * @return {?} Whether the element is scrolled out of view */ function isElementScrolledOutsideView(element, scrollContainers) { return scrollContainers.some((/** * @param {?} containerBounds * @return {?} */ containerBounds => { /** @type {?} */ const outsideAbove = element.bottom < containerBounds.top; /** @type {?} */ const outsideBelow = element.top > containerBounds.bottom; /** @type {?} */ const outsideLeft = element.right < containerBounds.left; /** @type {?} */ const outsideRight = element.left > containerBounds.right; return outsideAbove || outsideBelow || outsideLeft || outsideRight; })); } /** * Gets whether an element is clipped by any of its scrolling containers. * \@docs-private * @param {?} element Dimensions of the element (from getBoundingClientRect) * @param {?} scrollContainers Dimensions of element's scrolling containers (from getBoundingClientRect) * @return {?} Whether the element is clipped */ function isElementClippedByScrolling(element, scrollContainers) { return scrollContainers.some((/** * @param {?} scrollContainerRect * @return {?} */ scrollContainerRect => { /** @type {?} */ const clippedAbove = element.top < scrollContainerRect.top; /** @type {?} */ const clippedBelow = element.bottom > scrollContainerRect.bottom; /** @type {?} */ const clippedLeft = element.left < scrollContainerRect.left; /** @type {?} */ const clippedRight = element.right > scrollContainerRect.right; return clippedAbove || clippedBelow || clippedLeft || clippedRight; })); } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/scroll/reposition-scroll-strategy.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Config options for the RepositionScrollStrategy. * @record */ function RepositionScrollStrategyConfig() { } if (false) { /** * Time in milliseconds to throttle the scroll events. * @type {?|undefined} */ RepositionScrollStrategyConfig.prototype.scrollThrottle; /** * Whether to close the overlay once the user has scrolled away completely. * @type {?|undefined} */ RepositionScrollStrategyConfig.prototype.autoClose; } /** * Strategy that will update the element position as the user is scrolling. */ class RepositionScrollStrategy { /** * @param {?} _scrollDispatcher * @param {?} _viewportRuler * @param {?} _ngZone * @param {?=} _config */ constructor(_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. * @param {?} overlayRef * @return {?} */ attach(overlayRef) { if (this._overlayRef) { throw getMatScrollStrategyAlreadyAttachedError(); } this._overlayRef = overlayRef; } /** * Enables repositioning of the attached overlay on scroll. * @return {?} */ enable() { if (!this._scrollSubscription) { /** @type {?} */ const throttle = this._config ? this._config.scrollThrottle : 0; this._scrollSubscription = this._scrollDispatcher.scrolled(throttle).subscribe((/** * @return {?} */ () => { this._overlayRef.updatePosition(); // TODO(crisbeto): make `close` on by default once all components can handle it. if (this._config && this._config.autoClose) { /** @type {?} */ const overlayRect = this._overlayRef.overlayElement.getBoundingClientRect(); const { width, height } = this._viewportRuler.getViewportSize(); // TODO(crisbeto): include all ancestor scroll containers here once // we have a way of exposing the trigger element to the scroll strategy. /** @type {?} */ const parentRects = [{ width, height, bottom: height, right: width, top: 0, left: 0 }]; if (isElementScrolledOutsideView(overlayRect, parentRects)) { this.disable(); this._ngZone.run((/** * @return {?} */ () => this._overlayRef.detach())); } } })); } } /** * Disables repositioning of the attached overlay on scroll. * @return {?} */ disable() { if (this._scrollSubscription) { this._scrollSubscription.unsubscribe(); this._scrollSubscription = null; } } /** * @return {?} */ detach() { this.disable(); this._overlayRef = (/** @type {?} */ (null)); } } if (false) { /** * @type {?} * @private */ RepositionScrollStrategy.prototype._scrollSubscription; /** * @type {?} * @private */ RepositionScrollStrategy.prototype._overlayRef; /** * @type {?} * @private */ RepositionScrollStrategy.prototype._scrollDispatcher; /** * @type {?} * @private */ RepositionScrollStrategy.prototype._viewportRuler; /** * @type {?} * @private */ RepositionScrollStrategy.prototype._ngZone; /** * @type {?} * @private */ RepositionScrollStrategy.prototype._config; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/scroll/scroll-strategy-options.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * 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. */ class ScrollStrategyOptions { /** * @param {?} _scrollDispatcher * @param {?} _viewportRuler * @param {?} _ngZone * @param {?} document */ constructor(_scrollDispatcher, _viewportRuler, _ngZone, document) { this._scrollDispatcher = _scrollDispatcher; this._viewportRuler = _viewportRuler; this._ngZone = _ngZone; /** * Do nothing on scroll. */ this.noop = (/** * @return {?} */ () => new NoopScrollStrategy()); /** * Close the overlay as soon as the user scrolls. * @param config Configuration to be used inside the scroll strategy. */ this.close = (/** * @param {?=} config * @return {?} */ (config) => new CloseScrollStrategy(this._scrollDispatcher, this._ngZone, this._viewportRuler, config)); /** * Block scrolling. */ this.block = (/** * @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 = (/** * @param {?=} config * @return {?} */ (config) => new RepositionScrollStrategy(this._scrollDispatcher, this._viewportRuler, this._ngZone, config)); this._document = document; } } ScrollStrategyOptions.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; /** @nocollapse */ ScrollStrategyOptions.ctorParameters = () => [ { type: ScrollDispatcher }, { type: ViewportRuler }, { type: NgZone }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; /** @nocollapse */ ScrollStrategyOptions.ɵprov = ɵɵdefineInjectable({ factory: function ScrollStrategyOptions_Factory() { return new ScrollStrategyOptions(ɵɵinject(ScrollDispatcher), ɵɵinject(ViewportRuler), ɵɵinject(NgZone), ɵɵinject(DOCUMENT)); }, token: ScrollStrategyOptions, providedIn: "root" }); if (false) { /** * @type {?} * @private */ ScrollStrategyOptions.prototype._document; /** * Do nothing on scroll. * @type {?} */ ScrollStrategyOptions.prototype.noop; /** * Close the overlay as soon as the user scrolls. * \@param config Configuration to be used inside the scroll strategy. * @type {?} */ ScrollStrategyOptions.prototype.close; /** * Block scrolling. * @type {?} */ ScrollStrategyOptions.prototype.block; /** * Update the overlay's position on scroll. * \@param config Configuration to be used inside the scroll strategy. * Allows debouncing the reposition calls. * @type {?} */ ScrollStrategyOptions.prototype.reposition; /** * @type {?} * @private */ ScrollStrategyOptions.prototype._scrollDispatcher; /** * @type {?} * @private */ ScrollStrategyOptions.prototype._viewportRuler; /** * @type {?} * @private */ ScrollStrategyOptions.prototype._ngZone; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/scroll/index.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/overlay-config.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Initial configuration used when creating an overlay. */ class OverlayConfig { /** * @param {?=} config */ constructor(config) { /** * 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` /** @type {?} */ const configKeys = (/** @type {?} */ (Object.keys(config))); for (const key of configKeys) { 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] = (/** @type {?} */ (config[key])); } } } } } if (false) { /** * Strategy with which to position the overlay. * @type {?} */ OverlayConfig.prototype.positionStrategy; /** * Strategy to be used when handling scroll events while the overlay is open. * @type {?} */ OverlayConfig.prototype.scrollStrategy; /** * Custom class to add to the overlay pane. * @type {?} */ OverlayConfig.prototype.panelClass; /** * Whether the overlay has a backdrop. * @type {?} */ OverlayConfig.prototype.hasBackdrop; /** * Custom class to add to the backdrop * @type {?} */ OverlayConfig.prototype.backdropClass; /** * The width of the overlay panel. If a number is provided, pixel units are assumed. * @type {?} */ OverlayConfig.prototype.width; /** * The height of the overlay panel. If a number is provided, pixel units are assumed. * @type {?} */ OverlayConfig.prototype.height; /** * The min-width of the overlay panel. If a number is provided, pixel units are assumed. * @type {?} */ OverlayConfig.prototype.minWidth; /** * The min-height of the overlay panel. If a number is provided, pixel units are assumed. * @type {?} */ OverlayConfig.prototype.minHeight; /** * The max-width of the overlay panel. If a number is provided, pixel units are assumed. * @type {?} */ OverlayConfig.prototype.maxWidth; /** * The max-height of the overlay panel. If a number is provided, pixel units are assumed. * @type {?} */ OverlayConfig.prototype.maxHeight; /** * Direction of the text in the overlay panel. If a `Directionality` instance * is passed in, the overlay will handle changes to its value automatically. * @type {?} */ OverlayConfig.prototype.direction; /** * 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`). * @type {?} */ OverlayConfig.prototype.disposeOnNavigation; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/position/connected-position.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * A connection point on the origin element. * @record */ function OriginConnectionPosition() { } if (false) { /** @type {?} */ OriginConnectionPosition.prototype.originX; /** @type {?} */ OriginConnectionPosition.prototype.originY; } /** * A connection point on the overlay element. * @record */ function OverlayConnectionPosition() { } if (false) { /** @type {?} */ OverlayConnectionPosition.prototype.overlayX; /** @type {?} */ OverlayConnectionPosition.prototype.overlayY; } /** * The points of the origin element and the overlay element to connect. */ class ConnectionPositionPair { /** * @param {?} origin * @param {?} overlay * @param {?=} offsetX * @param {?=} offsetY * @param {?=} panelClass */ constructor(origin, overlay, offsetX, offsetY, 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; } } if (false) { /** * X-axis attachment point for connected overlay origin. Can be 'start', 'end', or 'center'. * @type {?} */ ConnectionPositionPair.prototype.originX; /** * Y-axis attachment point for connected overlay origin. Can be 'top', 'bottom', or 'center'. * @type {?} */ ConnectionPositionPair.prototype.originY; /** * X-axis attachment point for connected overlay. Can be 'start', 'end', or 'center'. * @type {?} */ ConnectionPositionPair.prototype.overlayX; /** * Y-axis attachment point for connected overlay. Can be 'top', 'bottom', or 'center'. * @type {?} */ ConnectionPositionPair.prototype.overlayY; /** * Offset along the X axis. * @type {?} */ ConnectionPositionPair.prototype.offsetX; /** * Offset along the Y axis. * @type {?} */ ConnectionPositionPair.prototype.offsetY; /** * Class(es) to be applied to the panel while this position is active. * @type {?} */ ConnectionPositionPair.prototype.panelClass; } /** * 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 */ class ScrollingVisibility { } if (false) { /** @type {?} */ ScrollingVisibility.prototype.isOriginClipped; /** @type {?} */ ScrollingVisibility.prototype.isOriginOutsideView; /** @type {?} */ ScrollingVisibility.prototype.isOverlayClipped; /** @type {?} */ ScrollingVisibility.prototype.isOverlayOutsideView; } /** * The change event emitted by the strategy when a fallback position is used. */ class ConnectedOverlayPositionChange { /** * @param {?} connectionPair * @param {?} scrollableViewProperties */ constructor(connectionPair, scrollableViewProperties) { this.connectionPair = connectionPair; this.scrollableViewProperties = scrollableViewProperties; } } /** @nocollapse */ ConnectedOverlayPositionChange.ctorParameters = () => [ { type: ConnectionPositionPair }, { type: ScrollingVisibility, decorators: [{ type: Optional }] } ]; if (false) { /** * The position used as a result of this change. * @type {?} */ ConnectedOverlayPositionChange.prototype.connectionPair; /** * \@docs-private * @type {?} */ ConnectedOverlayPositionChange.prototype.scrollableViewProperties; } /** * Validates whether a vertical position property matches the expected values. * \@docs-private * @param {?} property Name of the property being validated. * @param {?} value Value of the property being validated. * @return {?} */ 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. * \@docs-private * @param {?} property Name of the property being validated. * @param {?} value Value of the property being validated. * @return {?} */ function validateHorizontalPosition(property, value) { if (value !== 'start' && value !== 'end' && value !== 'center') { throw Error(`ConnectedPosition: Invalid ${property} "${value}". ` + `Expected "start", "end" or "center".`); } } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/keyboard/overlay-keyboard-dispatcher.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * 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. */ class OverlayKeyboardDispatcher { /** * @param {?} document */ constructor(document) { /** * Currently attached overlays in the order they were attached. */ this._attachedOverlays = []; /** * Keyboard event listener that will be attached to the body. */ this._keydownListener = (/** * @param {?} event * @return {?} */ (event) => { /** @type {?} */ const overlays = this._attachedOverlays; for (let 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; } /** * @return {?} */ ngOnDestroy() { this._detach(); } /** * Add a new overlay to the list of attached overlay refs. * @param {?} overlayRef * @return {?} */ add(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. * @param {?} overlayRef * @return {?} */ remove(overlayRef) { /** @type {?} */ const 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. * @private * @return {?} */ _detach() { if (this._isAttached) { this._document.body.removeEventListener('keydown', this._keydownListener); this._isAttached = false; } } } OverlayKeyboardDispatcher.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; /** @nocollapse */ OverlayKeyboardDispatcher.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; /** @nocollapse */ OverlayKeyboardDispatcher.ɵprov = ɵɵdefineInjectable({ factory: function OverlayKeyboardDispatcher_Factory() { return new OverlayKeyboardDispatcher(ɵɵinject(DOCUMENT)); }, token: OverlayKeyboardDispatcher, providedIn: "root" }); if (false) { /** * Currently attached overlays in the order they were attached. * @type {?} */ OverlayKeyboardDispatcher.prototype._attachedOverlays; /** * @type {?} * @private */ OverlayKeyboardDispatcher.prototype._document; /** * @type {?} * @private */ OverlayKeyboardDispatcher.prototype._isAttached; /** * Keyboard event listener that will be attached to the body. * @type {?} * @private */ OverlayKeyboardDispatcher.prototype._keydownListener; } /** * \@docs-private \@deprecated \@breaking-change 8.0.0 * @param {?} dispatcher * @param {?} _document * @return {?} */ function OVERLAY_KEYBOARD_DISPATCHER_PROVIDER_FACTORY(dispatcher, _document) { return dispatcher || new OverlayKeyboardDispatcher(_document); } /** * \@docs-private \@deprecated \@breaking-change 8.0.0 * @type {?} */ const OVERLAY_KEYBOARD_DISPATCHER_PROVIDER = { // If there is already an OverlayKeyboardDispatcher available, use that. // Otherwise, provide a new one. provide: OverlayKeyboardDispatcher, deps: [ [new Optional(), new SkipSelf(), OverlayKeyboardDispatcher], (/** @type {?} */ ( // Coerce to `InjectionToken` so that the `deps` match the "shape" // of the type expected by Angular DOCUMENT)) ], useFactory: OVERLAY_KEYBOARD_DISPATCHER_PROVIDER_FACTORY }; /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/overlay-container.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Whether we're in a testing environment. * TODO(crisbeto): remove this once we have an overlay testing module. * @type {?} */ const isTestEnvironment = typeof window !== 'undefined' && !!window && !!(((/** @type {?} */ (window))).__karma__ || ((/** @type {?} */ (window))).jasmine); /** * Container inside which all overlays will render. */ class OverlayContainer { /** * @param {?} document * @param {?=} _platform */ constructor(document, _platform) { this._platform = _platform; this._document = document; } /** * @return {?} */ ngOnDestroy() { /** @type {?} */ const 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. * @return {?} the container element */ getContainerElement() { 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. * @protected * @return {?} */ _createContainer() { // @breaking-change 10.0.0 Remove null check for `_platform`. /** @type {?} */ const isBrowser = this._platform ? this._platform.isBrowser : typeof window !== 'undefined'; /** @type {?} */ const containerClass = 'cdk-overlay-container'; if (isBrowser || isTestEnvironment) { /** @type {?} */ const 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 (let i = 0; i < oppositePlatformContainers.length; i++) { (/** @type {?} */ (oppositePlatformContainers[i].parentNode)).removeChild(oppositePlatformContainers[i]); } } /** @type {?} */ const 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: Injectable, args: [{ providedIn: 'root' },] } ]; /** @nocollapse */ OverlayContainer.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: Platform } ]; /** @nocollapse */ OverlayContainer.ɵprov = ɵɵdefineInjectable({ factory: function OverlayContainer_Factory() { return new OverlayContainer(ɵɵinject(DOCUMENT), ɵɵinject(Platform)); }, token: OverlayContainer, providedIn: "root" }); if (false) { /** * @type {?} * @protected */ OverlayContainer.prototype._containerElement; /** * @type {?} * @protected */ OverlayContainer.prototype._document; /** * @deprecated `platform` parameter to become required. * \@breaking-change 10.0.0 * @type {?} * @protected */ OverlayContainer.prototype._platform; } /** * \@docs-private \@deprecated \@breaking-change 8.0.0 * @param {?} parentContainer * @param {?} _document * @return {?} */ function OVERLAY_CONTAINER_PROVIDER_FACTORY(parentContainer, _document) { return parentContainer || new OverlayContainer(_document); } /** * \@docs-private \@deprecated \@breaking-change 8.0.0 * @type {?} */ const OVERLAY_CONTAINER_PROVIDER = { // If there is already an OverlayContainer available, use that. Otherwise, provide a new one. provide: OverlayContainer, deps: [ [new Optional(), new SkipSelf(), OverlayContainer], (/** @type {?} */ (DOCUMENT)) ], useFactory: OVERLAY_CONTAINER_PROVIDER_FACTORY }; /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/overlay-ref.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Reference to an overlay that has been created with the Overlay service. * Used to manipulate or dispose of said overlay. */ class OverlayRef { /** * @param {?} _portalOutlet * @param {?} _host * @param {?} _pane * @param {?} _config * @param {?} _ngZone * @param {?} _keyboardDispatcher * @param {?} _document * @param {?=} _location */ constructor(_portalOutlet, _host, _pane, _config, _ngZone, _keyboardDispatcher, _document, _location) { 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 Subject(); this._attachments = new Subject(); this._detachments = new Subject(); this._locationChanges = Subscription.EMPTY; this._backdropClickHandler = (/** * @param {?} event * @return {?} */ (event) => this._backdropClick.next(event)); this._keydownEventsObservable = new Observable((/** * @param {?} observer * @return {?} */ (observer) => { /** @type {?} */ const subscription = this._keydownEvents.subscribe(observer); this._keydownEventSubscriptions++; return (/** * @return {?} */ () => { subscription.unsubscribe(); this._keydownEventSubscriptions--; }); })); /** * Stream of keydown events dispatched to this overlay. */ this._keydownEvents = new 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; } /** * The overlay's HTML element * @return {?} */ get overlayElement() { return this._pane; } /** * The overlay's backdrop HTML element. * @return {?} */ get backdropElement() { return this._backdropElement; } /** * Wrapper around the panel element. Can be used for advanced * positioning where a wrapper with specific styling is * required around the overlay pane. * @return {?} */ get hostElement() { return this._host; } /** * 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. * @return {?} The portal attachment result. */ attach(portal) { /** @type {?} */ let attachResult = this._portalOutlet.attach(portal); // Update the pane element with the given configuration. if (!this._host.parentElement && this._previousHostParent) { this._previousHostParent.appendChild(this._host); } if (this._positionStrategy) { this._positionStrategy.attach(this); } 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(take(1)) .subscribe((/** * @return {?} */ () => { // 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((/** * @return {?} */ () => this.dispose())); } return attachResult; } /** * Detaches an overlay from a portal. * @return {?} The portal detachment result. */ detach() { 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(); } /** @type {?} */ const 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. * @return {?} */ dispose() { /** @type {?} */ const 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 = (/** @type {?} */ (null)); } this._previousHostParent = this._pane = (/** @type {?} */ (null)); if (isAttached) { this._detachments.next(); } this._detachments.complete(); } /** * Whether the overlay has attached content. * @return {?} */ hasAttached() { return this._portalOutlet.hasAttached(); } /** * Gets an observable that emits when the backdrop has been clicked. * @return {?} */ backdropClick() { return this._backdropClick.asObservable(); } /** * Gets an observable that emits when the overlay has been attached. * @return {?} */ attachments() { return this._attachments.asObservable(); } /** * Gets an observable that emits when the overlay has been detached. * @return {?} */ detachments() { return this._detachments.asObservable(); } /** * Gets an observable of keydown events targeted to this overlay. * @return {?} */ keydownEvents() { return this._keydownEventsObservable; } /** * Gets the current overlay configuration, which is immutable. * @return {?} */ getConfig() { return this._config; } /** * Updates the position of the overlay based on the position strategy. * @return {?} */ updatePosition() { if (this._positionStrategy) { this._positionStrategy.apply(); } } /** * Switches to a new position strategy and updates the overlay position. * @param {?} strategy * @return {?} */ updatePositionStrategy(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. * @param {?} sizeConfig * @return {?} */ updateSize(sizeConfig) { this._config = Object.assign(Object.assign({}, this._config), sizeConfig); this._updateElementSize(); } /** * Sets the LTR/RTL direction for the overlay. * @param {?} dir * @return {?} */ setDirection(dir) { this._config = Object.assign(Object.assign({}, this._config), { direction: dir }); this._updateElementDirection(); } /** * Add a CSS class or an array of classes to the overlay pane. * @param {?} classes * @return {?} */ addPanelClass(classes) { if (this._pane) { this._toggleClasses(this._pane, classes, true); } } /** * Remove a CSS class or an array of classes from the overlay pane. * @param {?} classes * @return {?} */ removePanelClass(classes) { if (this._pane) { this._toggleClasses(this._pane, classes, false); } } /** * Returns the layout direction of the overlay panel. * @return {?} */ getDirection() { /** @type {?} */ const direction = this._config.direction; if (!direction) { return 'ltr'; } return typeof direction === 'string' ? direction : direction.value; } /** * Switches to a new scroll strategy. * @param {?} strategy * @return {?} */ updateScrollStrategy(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. * @private * @return {?} */ _updateElementDirection() { this._host.setAttribute('dir', this.getDirection()); } /** * Updates the size of the overlay element based on the overlay config. * @private * @return {?} */ _updateElementSize() { if (!this._pane) { return; } /** @type {?} */ const style = this._pane.style; style.width = coerceCssPixelValue(this._config.width); style.height = coerceCssPixelValue(this._config.height); style.minWidth = coerceCssPixelValue(this._config.minWidth); style.minHeight = coerceCssPixelValue(this._config.minHeight); style.maxWidth = coerceCssPixelValue(this._config.maxWidth); style.maxHeight = coerceCssPixelValue(this._config.maxHeight); } /** * Toggles the pointer events for the overlay pane element. * @private * @param {?} enablePointer * @return {?} */ _togglePointerEvents(enablePointer) { this._pane.style.pointerEvents = enablePointer ? 'auto' : 'none'; } /** * Attaches a backdrop for this overlay. * @private * @return {?} */ _attachBackdrop() { /** @type {?} */ const 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. (/** @type {?} */ (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((/** * @return {?} */ () => { requestAnimationFrame((/** * @return {?} */ () => { 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 stacking will be wrong, because the detached element's pane will still be * in its original DOM position. * @private * @return {?} */ _updateStackingOrder() { if (this._host.nextSibling) { (/** @type {?} */ (this._host.parentNode)).appendChild(this._host); } } /** * Detaches the backdrop (if any) associated with the overlay. * @return {?} */ detachBackdrop() { /** @type {?} */ let backdropToDetach = this._backdropElement; if (!backdropToDetach) { return; } /** @type {?} */ let timeoutId; /** @type {?} */ let finishDetach = (/** * @return {?} */ () => { // It may not be attached to anything in certain cases (e.g. unit tests). if (backdropToDetach) { backdropToDetach.removeEventListener('click', this._backdropClickHandler); backdropToDetach.removeEventListener('transitionend', finishDetach); if (backdropToDetach.parentNode) { backdropToDetach.parentNode.removeChild(backdropToDetach); } } // It is possible that a new portal has been attached to this overlay since we started // removing the backdrop. If that is the case, only clear the backdrop reference if it // is still the same instance that we started to remove. if (this._backdropElement == backdropToDetach) { this._backdropElement = null; } if (this._config.backdropClass) { this._toggleClasses((/** @type {?} */ (backdropToDetach)), this._config.backdropClass, false); } clearTimeout(timeoutId); }); backdropToDetach.classList.remove('cdk-overlay-backdrop-showing'); this._ngZone.runOutsideAngular((/** * @return {?} */ () => { (/** @type {?} */ (backdropToDetach)).addEventListener('transitionend', finishDetach); })); // If the backdrop doesn't have a transition, the `transitionend` event won't fire. // In this case we make it unclickable and we try to remove it after a delay. backdropToDetach.style.pointerEvents = 'none'; // Run this outside the Angular zone because there's nothing that Angular cares about. // If it were to run inside the Angular zone, every test that used Overlay would have to be // either async or fakeAsync. timeoutId = this._ngZone.runOutsideAngular((/** * @return {?} */ () => setTimeout(finishDetach, 500))); } /** * Toggles a single CSS class or an array of classes on an element. * @private * @param {?} element * @param {?} cssClasses * @param {?} isAdd * @return {?} */ _toggleClasses(element, cssClasses, isAdd) { /** @type {?} */ const classList = element.classList; coerceArray(cssClasses).forEach((/** * @param {?} cssClass * @return {?} */ cssClass => { // We can't do a spread here, because IE doesn't support setting multiple classes. // Also trying to add an empty string to a DOMTokenList will throw. if (cssClass) { isAdd ? classList.add(cssClass) : classList.remove(cssClass); } })); } /** * Detaches the overlay content next time the zone stabilizes. * @private * @return {?} */ _detachContentWhenStable() { // Normally we wouldn't have to explicitly run this outside the `NgZone`, however // if the consumer is using `zone-patch-rxjs`, the `Subscription.unsubscribe` call will // be patched to run inside the zone, which will throw us into an infinite loop. this._ngZone.runOutsideAngular((/** * @return {?} */ () => { // We can't remove the host here immediately, because the overlay pane's content // might still be animating. This stream helps us avoid interrupting the animation // by waiting for the pane to become empty. /** @type {?} */ const subscription = this._ngZone.onStable .asObservable() .pipe(takeUntil(merge(this._attachments, this._detachments))) .subscribe((/** * @return {?} */ () => { // Needs a couple of checks for the pane and host, because // they may have been removed by the time the zone stabilizes. if (!this._pane || !this._host || this._pane.children.length === 0) { if (this._pane && this._config.panelClass) { this._toggleClasses(this._pane, this._config.panelClass, false); } if (this._host && this._host.parentElement) { this._previousHostParent = this._host.parentElement; this._previousHostParent.removeChild(this._host); } subscription.unsubscribe(); } })); })); } /** * Disposes of a scroll strategy. * @private * @return {?} */ _disposeScrollStrategy() { /** @type {?} */ const scrollStrategy = this._scrollStrategy; if (scrollStrategy) { scrollStrategy.disable(); if (scrollStrategy.detach) { scrollStrategy.detach(); } } } } if (false) { /** * @type {?} * @private */ OverlayRef.prototype._backdropElement; /** * @type {?} * @private */ OverlayRef.prototype._backdropClick; /** * @type {?} * @private */ OverlayRef.prototype._attachments; /** * @type {?} * @private */ OverlayRef.prototype._detachments; /** * @type {?} * @private */ OverlayRef.prototype._positionStrategy; /** * @type {?} * @private */ OverlayRef.prototype._scrollStrategy; /** * @type {?} * @private */ OverlayRef.prototype._locationChanges; /** * @type {?} * @private */ OverlayRef.prototype._backdropClickHandler; /** * Reference to the parent of the `_host` at the time it was detached. Used to restore * the `_host` to its original position in the DOM when it gets re-attached. * @type {?} * @private */ OverlayRef.prototype._previousHostParent; /** * @type {?} * @private */ OverlayRef.prototype._keydownEventsObservable; /** * Stream of keydown events dispatched to this overlay. * @type {?} */ OverlayRef.prototype._keydownEvents; /** * Amount of subscriptions to the keydown events. * @type {?} */ OverlayRef.prototype._keydownEventSubscriptions; /** * @type {?} * @private */ OverlayRef.prototype._portalOutlet; /** * @type {?} * @private */ OverlayRef.prototype._host; /** * @type {?} * @private */ OverlayRef.prototype._pane; /** * @type {?} * @private */ OverlayRef.prototype._config; /** * @type {?} * @private */ OverlayRef.prototype._ngZone; /** * @type {?} * @private */ OverlayRef.prototype._keyboardDispatcher; /** * @type {?} * @private */ OverlayRef.prototype._document; /** * @type {?} * @private */ OverlayRef.prototype._location; } /** * Size properties for an overlay. * @record */ function OverlaySizeConfig() { } if (false) { /** @type {?|undefined} */ OverlaySizeConfig.prototype.width; /** @type {?|undefined} */ OverlaySizeConfig.prototype.height; /** @type {?|undefined} */ OverlaySizeConfig.prototype.minWidth; /** @type {?|undefined} */ OverlaySizeConfig.prototype.minHeight; /** @type {?|undefined} */ OverlaySizeConfig.prototype.maxWidth; /** @type {?|undefined} */ OverlaySizeConfig.prototype.maxHeight; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/position/flexible-connected-position-strategy.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // TODO: refactor clipping detection into a separate thing (part of scrolling module) // TODO: doesn't handle both flexible width and height when it has to scroll along both axis. /** * Class to be added to the overlay bounding box. * @type {?} */ const boundingBoxClass = 'cdk-overlay-connected-position-bounding-box'; /** * Regex used to split a string on its CSS units. * @type {?} */ const cssUnitPattern = /([A-Za-z%]+)$/; /** * A strategy for positioning overlays. Using this strategy, an overlay is given an * implicit position relative some origin element. The relative position is defined in terms of * a point on the origin element that is connected to a point on the overlay element. For example, * a basic dropdown is connecting the bottom-left corner of the origin to the top-left corner * of the overlay. */ class FlexibleConnectedPositionStrategy { /** * @param {?} connectedTo * @param {?} _viewportRuler * @param {?} _document * @param {?} _platform * @param {?} _overlayContainer */ constructor(connectedTo, _viewportRuler, _document, _platform, _overlayContainer) { this._viewportRuler = _viewportRuler; this._document = _document; this._platform = _platform; this._overlayContainer = _overlayContainer; /** * Last size used for the bounding box. Used to avoid resizing the overlay after open. */ this._lastBoundingBoxSize = { width: 0, height: 0 }; /** * Whether the overlay was pushed in a previous positioning. */ this._isPushed = false; /** * Whether the overlay can be pushed on-screen on the initial open. */ this._canPush = true; /** * Whether the overlay can grow via flexible width/height after the initial open. */ this._growAfterOpen = false; /** * Whether the overlay's width and height can be constrained to fit within the viewport. */ this._hasFlexibleDimensions = true; /** * Whether the overlay position is locked. */ this._positionLocked = false; /** * Amount of space that must be maintained between the overlay and the edge of the viewport. */ this._viewportMargin = 0; /** * The Scrollable containers used to check scrollable view properties on position change. */ this._scrollables = []; /** * Ordered list of preferred positions, from most to least desirable. */ this._preferredPositions = []; /** * Subject that emits whenever the position changes. */ this._positionChanges = new Subject(); /** * Subscription to viewport size changes. */ this._resizeSubscription = Subscription.EMPTY; /** * Default offset for the overlay along the x axis. */ this._offsetX = 0; /** * Default offset for the overlay along the y axis. */ this._offsetY = 0; /** * Keeps track of the CSS classes that the position strategy has applied on the overlay panel. */ this._appliedPanelClasses = []; /** * Observable sequence of position changes. */ this.positionChanges = this._positionChanges.asObservable(); this.setOrigin(connectedTo); } /** * Ordered list of preferred positions, from most to least desirable. * @return {?} */ get positions() { return this._preferredPositions; } /** * Attaches this position strategy to an overlay. * @param {?} overlayRef * @return {?} */ attach(overlayRef) { if (this._overlayRef && overlayRef !== this._overlayRef) { throw Error('This position strategy is already attached to an overlay'); } this._validatePositions(); overlayRef.hostElement.classList.add(boundingBoxClass); this._overlayRef = overlayRef; this._boundingBox = overlayRef.hostElement; this._pane = overlayRef.overlayElement; this._isDisposed = false; this._isInitialRender = true; this._lastPosition = null; this._resizeSubscription.unsubscribe(); this._resizeSubscription = this._viewportRuler.change().subscribe((/** * @return {?} */ () => { // When the window is resized, we want to trigger the next reposition as if it // was an initial render, in order for the strategy to pick a new optimal position, // otherwise position locking will cause it to stay at the old one. this._isInitialRender = true; this.apply(); })); } /** * Updates the position of the overlay element, using whichever preferred position relative * to the origin best fits on-screen. * * The selection of a position goes as follows: * - If any positions fit completely within the viewport as-is, * choose the first position that does so. * - If flexible dimensions are enabled and at least one satifies the given minimum width/height, * choose the position with the greatest available size modified by the positions' weight. * - If pushing is enabled, take the position that went off-screen the least and push it * on-screen. * - If none of the previous criteria were met, use the position that goes off-screen the least. * \@docs-private * @return {?} */ apply() { // We shouldn't do anything if the strategy was disposed or we're on the server. if (this._isDisposed || !this._platform.isBrowser) { return; } // If the position has been applied already (e.g. when the overlay was opened) and the // consumer opted into locking in the position, re-use the old position, in order to // prevent the overlay from jumping around. if (!this._isInitialRender && this._positionLocked && this._lastPosition) { this.reapplyLastPosition(); return; } this._clearPanelClasses(); this._resetOverlayElementStyles(); this._resetBoundingBoxStyles(); // We need the bounding rects for the origin and the overlay to determine how to position // the overlay relative to the origin. // We use the viewport rect to determine whether a position would go off-screen. this._viewportRect = this._getNarrowedViewportRect(); this._originRect = this._getOriginRect(); this._overlayRect = this._pane.getBoundingClientRect(); /** @type {?} */ const originRect = this._originRect; /** @type {?} */ const overlayRect = this._overlayRect; /** @type {?} */ const viewportRect = this._viewportRect; // Positions where the overlay will fit with flexible dimensions. /** @type {?} */ const flexibleFits = []; // Fallback if none of the preferred positions fit within the viewport. /** @type {?} */ let fallback; // Go through each of the preferred positions looking for a good fit. // If a good fit is found, it will be applied immediately. for (let pos of this._preferredPositions) { // Get the exact (x, y) coordinate for the point-of-origin on the origin element. /** @type {?} */ let originPoint = this._getOriginPoint(originRect, pos); // From that point-of-origin, get the exact (x, y) coordinate for the top-left corner of the // overlay in this position. We use the top-left corner for calculations and later translate // this into an appropriate (top, left, bottom, right) style. /** @type {?} */ let overlayPoint = this._getOverlayPoint(originPoint, overlayRect, pos); // Calculate how well the overlay would fit into the viewport with this point. /** @type {?} */ let overlayFit = this._getOverlayFit(overlayPoint, overlayRect, viewportRect, pos); // If the overlay, without any further work, fits into the viewport, use this position. if (overlayFit.isCompletelyWithinViewport) { this._isPushed = false; this._applyPosition(pos, originPoint); return; } // If the overlay has flexible dimensions, we can use this position // so long as there's enough space for the minimum dimensions. if (this._canFitWithFlexibleDimensions(overlayFit, overlayPoint, viewportRect)) { // Save positions where the overlay will fit with flexible dimensions. We will use these // if none of the positions fit *without* flexible dimensions. flexibleFits.push({ position: pos, origin: originPoint, overlayRect, boundingBoxRect: this._calculateBoundingBoxRect(originPoint, pos) }); continue; } // If the current preferred position does not fit on the screen, remember the position // if it has more visible area on-screen than we've seen and move onto the next preferred // position. if (!fallback || fallback.overlayFit.visibleArea < overlayFit.visibleArea) { fallback = { overlayFit, overlayPoint, originPoint, position: pos, overlayRect }; } } // If there are any positions where the overlay would fit with flexible dimensions, choose the // one that has the greatest area available modified by the position's weight if (flexibleFits.length) { /** @type {?} */ let bestFit = null; /** @type {?} */ let bestScore = -1; for (const fit of flexibleFits) { /** @type {?} */ const score = fit.boundingBoxRect.width * fit.boundingBoxRect.height * (fit.position.weight || 1); if (score > bestScore) { bestScore = score; bestFit = fit; } } this._isPushed = false; this._applyPosition((/** @type {?} */ (bestFit)).position, (/** @type {?} */ (bestFit)).origin); return; } // When none of the preferred positions fit within the viewport, take the position // that went off-screen the least and attempt to push it on-screen. if (this._canPush) { // TODO(jelbourn): after pushing, the opening "direction" of the overlay might not make sense. this._isPushed = true; this._applyPosition((/** @type {?} */ (fallback)).position, (/** @type {?} */ (fallback)).originPoint); return; } // All options for getting the overlay within the viewport have been exhausted, so go with the // position that went off-screen the least. this._applyPosition((/** @type {?} */ (fallback)).position, (/** @type {?} */ (fallback)).originPoint); } /** * @return {?} */ detach() { this._clearPanelClasses(); this._lastPosition = null; this._previousPushAmount = null; this._resizeSubscription.unsubscribe(); } /** * Cleanup after the element gets destroyed. * @return {?} */ dispose() { if (this._isDisposed) { return; } // We can't use `_resetBoundingBoxStyles` here, because it resets // some properties to zero, rather than removing them. if (this._boundingBox) { extendStyles(this._boundingBox.style, (/** @type {?} */ ({ top: '', left: '', right: '', bottom: '', height: '', width: '', alignItems: '', justifyContent: '', }))); } if (this._pane) { this._resetOverlayElementStyles(); } if (this._overlayRef) { this._overlayRef.hostElement.classList.remove(boundingBoxClass); } this.detach(); this._positionChanges.complete(); this._overlayRef = this._boundingBox = (/** @type {?} */ (null)); this._isDisposed = true; } /** * This re-aligns the overlay element with the trigger in its last calculated position, * even if a position higher in the "preferred positions" list would now fit. This * allows one to re-align the panel without changing the orientation of the panel. * @return {?} */ reapplyLastPosition() { if (!this._isDisposed && (!this._platform || this._platform.isBrowser)) { this._originRect = this._getOriginRect(); this._overlayRect = this._pane.getBoundingClientRect(); this._viewportRect = this._getNarrowedViewportRect(); /** @type {?} */ const lastPosition = this._lastPosition || this._preferredPositions[0]; /** @type {?} */ const originPoint = this._getOriginPoint(this._originRect, lastPosition); this._applyPosition(lastPosition, originPoint); } } /** * Sets the list of Scrollable containers that host the origin element so that * on reposition we can evaluate if it or the overlay has been clipped or outside view. Every * Scrollable must be an ancestor element of the strategy's origin element. * @template THIS * @this {THIS} * @param {?} scrollables * @return {THIS} */ withScrollableContainers(scrollables) { (/** @type {?} */ (this))._scrollables = scrollables; return (/** @type {?} */ (this)); } /** * Adds new preferred positions. * @template THIS * @this {THIS} * @param {?} positions List of positions options for this overlay. * @return {THIS} */ withPositions(positions) { (/** @type {?} */ (this))._preferredPositions = positions; // If the last calculated position object isn't part of the positions anymore, clear // it in order to avoid it being picked up if the consumer tries to re-apply. if (positions.indexOf((/** @type {?} */ ((/** @type {?} */ (this))._lastPosition))) === -1) { (/** @type {?} */ (this))._lastPosition = null; } (/** @type {?} */ (this))._validatePositions(); return (/** @type {?} */ (this)); } /** * Sets a minimum distance the overlay may be positioned to the edge of the viewport. * @template THIS * @this {THIS} * @param {?} margin Required margin between the overlay and the viewport edge in pixels. * @return {THIS} */ withViewportMargin(margin) { (/** @type {?} */ (this))._viewportMargin = margin; return (/** @type {?} */ (this)); } /** * Sets whether the overlay's width and height can be constrained to fit within the viewport. * @template THIS * @this {THIS} * @param {?=} flexibleDimensions * @return {THIS} */ withFlexibleDimensions(flexibleDimensions = true) { (/** @type {?} */ (this))._hasFlexibleDimensions = flexibleDimensions; return (/** @type {?} */ (this)); } /** * Sets whether the overlay can grow after the initial open via flexible width/height. * @template THIS * @this {THIS} * @param {?=} growAfterOpen * @return {THIS} */ withGrowAfterOpen(growAfterOpen = true) { (/** @type {?} */ (this))._growAfterOpen = growAfterOpen; return (/** @type {?} */ (this)); } /** * Sets whether the overlay can be pushed on-screen if none of the provided positions fit. * @template THIS * @this {THIS} * @param {?=} canPush * @return {THIS} */ withPush(canPush = true) { (/** @type {?} */ (this))._canPush = canPush; return (/** @type {?} */ (this)); } /** * Sets whether the overlay's position should be locked in after it is positioned * initially. When an overlay is locked in, it won't attempt to reposition itself * when the position is re-applied (e.g. when the user scrolls away). * @template THIS * @this {THIS} * @param {?=} isLocked Whether the overlay should locked in. * @return {THIS} */ withLockedPosition(isLocked = true) { (/** @type {?} */ (this))._positionLocked = isLocked; return (/** @type {?} */ (this)); } /** * Sets the origin, relative to which to position the overlay. * Using an element origin is useful for building components that need to be positioned * relatively to a trigger (e.g. dropdown menus or tooltips), whereas using a point can be * used for cases like contextual menus which open relative to the user's pointer. * @template THIS * @this {THIS} * @param {?} origin Reference to the new origin. * @return {THIS} */ setOrigin(origin) { (/** @type {?} */ (this))._origin = origin; return (/** @type {?} */ (this)); } /** * Sets the default offset for the overlay's connection point on the x-axis. * @template THIS * @this {THIS} * @param {?} offset New offset in the X axis. * @return {THIS} */ withDefaultOffsetX(offset) { (/** @type {?} */ (this))._offsetX = offset; return (/** @type {?} */ (this)); } /** * Sets the default offset for the overlay's connection point on the y-axis. * @template THIS * @this {THIS} * @param {?} offset New offset in the Y axis. * @return {THIS} */ withDefaultOffsetY(offset) { (/** @type {?} */ (this))._offsetY = offset; return (/** @type {?} */ (this)); } /** * Configures that the position strategy should set a `transform-origin` on some elements * inside the overlay, depending on the current position that is being applied. This is * useful for the cases where the origin of an animation can change depending on the * alignment of the overlay. * @template THIS * @this {THIS} * @param {?} selector CSS selector that will be used to find the target * elements onto which to set the transform origin. * @return {THIS} */ withTransformOriginOn(selector) { (/** @type {?} */ (this))._transformOriginSelector = selector; return (/** @type {?} */ (this)); } /** * Gets the (x, y) coordinate of a connection point on the origin based on a relative position. * @private * @param {?} originRect * @param {?} pos * @return {?} */ _getOriginPoint(originRect, pos) { /** @type {?} */ let x; if (pos.originX == 'center') { // Note: when centering we should always use the `left` // offset, otherwise the position will be wrong in RTL. x = originRect.left + (originRect.width / 2); } else { /** @type {?} */ const startX = this._isRtl() ? originRect.right : originRect.left; /** @type {?} */ const endX = this._isRtl() ? originRect.left : originRect.right; x = pos.originX == 'start' ? startX : endX; } /** @type {?} */ let y; if (pos.originY == 'center') { y = originRect.top + (originRect.height / 2); } else { y = pos.originY == 'top' ? originRect.top : originRect.bottom; } return { x, y }; } /** * Gets the (x, y) coordinate of the top-left corner of the overlay given a given position and * origin point to which the overlay should be connected. * @private * @param {?} originPoint * @param {?} overlayRect * @param {?} pos * @return {?} */ _getOverlayPoint(originPoint, overlayRect, pos) { // Calculate the (overlayStartX, overlayStartY), the start of the // potential overlay position relative to the origin point. /** @type {?} */ let overlayStartX; if (pos.overlayX == 'center') { overlayStartX = -overlayRect.width / 2; } else if (pos.overlayX === 'start') { overlayStartX = this._isRtl() ? -overlayRect.width : 0; } else { overlayStartX = this._isRtl() ? 0 : -overlayRect.width; } /** @type {?} */ let overlayStartY; if (pos.overlayY == 'center') { overlayStartY = -overlayRect.height / 2; } else { overlayStartY = pos.overlayY == 'top' ? 0 : -overlayRect.height; } // The (x, y) coordinates of the overlay. return { x: originPoint.x + overlayStartX, y: originPoint.y + overlayStartY, }; } /** * Gets how well an overlay at the given point will fit within the viewport. * @private * @param {?} point * @param {?} overlay * @param {?} viewport * @param {?} position * @return {?} */ _getOverlayFit(point, overlay, viewport, position) { let { x, y } = point; /** @type {?} */ let offsetX = this._getOffset(position, 'x'); /** @type {?} */ let offsetY = this._getOffset(position, 'y'); // Account for the offsets since they could push the overlay out of the viewport. if (offsetX) { x += offsetX; } if (offsetY) { y += offsetY; } // How much the overlay would overflow at this position, on each side. /** @type {?} */ let leftOverflow = 0 - x; /** @type {?} */ let rightOverflow = (x + overlay.width) - viewport.width; /** @type {?} */ let topOverflow = 0 - y; /** @type {?} */ let bottomOverflow = (y + overlay.height) - viewport.height; // Visible parts of the element on each axis. /** @type {?} */ let visibleWidth = this._subtractOverflows(overlay.width, leftOverflow, rightOverflow); /** @type {?} */ let visibleHeight = this._subtractOverflows(overlay.height, topOverflow, bottomOverflow); /** @type {?} */ let visibleArea = visibleWidth * visibleHeight; return { visibleArea, isCompletelyWithinViewport: (overlay.width * overlay.height) === visibleArea, fitsInViewportVertically: visibleHeight === overlay.height, fitsInViewportHorizontally: visibleWidth == overlay.width, }; } /** * Whether the overlay can fit within the viewport when it may resize either its width or height. * @private * @param {?} fit How well the overlay fits in the viewport at some position. * @param {?} point The (x, y) coordinates of the overlat at some position. * @param {?} viewport The geometry of the viewport. * @return {?} */ _canFitWithFlexibleDimensions(fit, point, viewport) { if (this._hasFlexibleDimensions) { /** @type {?} */ const availableHeight = viewport.bottom - point.y; /** @type {?} */ const availableWidth = viewport.right - point.x; /** @type {?} */ const minHeight = getPixelValue(this._overlayRef.getConfig().minHeight); /** @type {?} */ const minWidth = getPixelValue(this._overlayRef.getConfig().minWidth); /** @type {?} */ const verticalFit = fit.fitsInViewportVertically || (minHeight != null && minHeight <= availableHeight); /** @type {?} */ const horizontalFit = fit.fitsInViewportHorizontally || (minWidth != null && minWidth <= availableWidth); return verticalFit && horizontalFit; } return false; } /** * Gets the point at which the overlay can be "pushed" on-screen. If the overlay is larger than * the viewport, the top-left corner will be pushed on-screen (with overflow occuring on the * right and bottom). * * @private * @param {?} start Starting point from which the overlay is pushed. * @param {?} overlay Dimensions of the overlay. * @param {?} scrollPosition Current viewport scroll position. * @return {?} The point at which to position the overlay after pushing. This is effectively a new * originPoint. */ _pushOverlayOnScreen(start, overlay, scrollPosition) { // If the position is locked and we've pushed the overlay already, reuse the previous push // amount, rather than pushing it again. If we were to continue pushing, the element would // remain in the viewport, which goes against the expectations when position locking is enabled. if (this._previousPushAmount && this._positionLocked) { return { x: start.x + this._previousPushAmount.x, y: start.y + this._previousPushAmount.y }; } /** @type {?} */ const viewport = this._viewportRect; // Determine how much the overlay goes outside the viewport on each // side, which we'll use to decide which direction to push it. /** @type {?} */ const overflowRight = Math.max(start.x + overlay.width - viewport.right, 0); /** @type {?} */ const overflowBottom = Math.max(start.y + overlay.height - viewport.bottom, 0); /** @type {?} */ const overflowTop = Math.max(viewport.top - scrollPosition.top - start.y, 0); /** @type {?} */ const overflowLeft = Math.max(viewport.left - scrollPosition.left - start.x, 0); // Amount by which to push the overlay in each axis such that it remains on-screen. /** @type {?} */ let pushX = 0; /** @type {?} */ let pushY = 0; // If the overlay fits completely within the bounds of the viewport, push it from whichever // direction is goes off-screen. Otherwise, push the top-left corner such that its in the // viewport and allow for the trailing end of the overlay to go out of bounds. if (overlay.width <= viewport.width) { pushX = overflowLeft || -overflowRight; } else { pushX = start.x < this._viewportMargin ? (viewport.left - scrollPosition.left) - start.x : 0; } if (overlay.height <= viewport.height) { pushY = overflowTop || -overflowBottom; } else { pushY = start.y < this._viewportMargin ? (viewport.top - scrollPosition.top) - start.y : 0; } this._previousPushAmount = { x: pushX, y: pushY }; return { x: start.x + pushX, y: start.y + pushY, }; } /** * Applies a computed position to the overlay and emits a position change. * @private * @param {?} position The position preference * @param {?} originPoint The point on the origin element where the overlay is connected. * @return {?} */ _applyPosition(position, originPoint) { this._setTransformOrigin(position); this._setOverlayElementStyles(originPoint, position); this._setBoundingBoxStyles(originPoint, position); if (position.panelClass) { this._addPanelClasses(position.panelClass); } // Save the last connected position in case the position needs to be re-calculated. this._lastPosition = position; // Notify that the position has been changed along with its change properties. // We only emit if we've got any subscriptions, because the scroll visibility // calculcations can be somewhat expensive. if (this._positionChanges.observers.length) { /** @type {?} */ const scrollableViewProperties = this._getScrollVisibility(); /** @type {?} */ const changeEvent = new ConnectedOverlayPositionChange(position, scrollableViewProperties); this._positionChanges.next(changeEvent); } this._isInitialRender = false; } /** * Sets the transform origin based on the configured selector and the passed-in position. * @private * @param {?} position * @return {?} */ _setTransformOrigin(position) { if (!this._transformOriginSelector) { return; } /** @type {?} */ const elements = (/** @type {?} */ (this._boundingBox)).querySelectorAll(this._transformOriginSelector); /** @type {?} */ let xOrigin; /** @type {?} */ let yOrigin = position.overlayY; if (position.overlayX === 'center') { xOrigin = 'center'; } else if (this._isRtl()) { xOrigin = position.overlayX === 'start' ? 'right' : 'left'; } else { xOrigin = position.overlayX === 'start' ? 'left' : 'right'; } for (let i = 0; i < elements.length; i++) { elements[i].style.transformOrigin = `${xOrigin} ${yOrigin}`; } } /** * Gets the position and size of the overlay's sizing container. * * This method does no measuring and applies no styles so that we can cheaply compute the * bounds for all positions and choose the best fit based on these results. * @private * @param {?} origin * @param {?} position * @return {?} */ _calculateBoundingBoxRect(origin, position) { /** @type {?} */ const viewport = this._viewportRect; /** @type {?} */ const isRtl = this._isRtl(); /** @type {?} */ let height; /** @type {?} */ let top; /** @type {?} */ let bottom; if (position.overlayY === 'top') { // Overlay is opening "downward" and thus is bound by the bottom viewport edge. top = origin.y; height = viewport.height - top + this._viewportMargin; } else if (position.overlayY === 'bottom') { // Overlay is opening "upward" and thus is bound by the top viewport edge. We need to add // the viewport margin back in, because the viewport rect is narrowed down to remove the // margin, whereas the `origin` position is calculated based on its `ClientRect`. bottom = viewport.height - origin.y + this._viewportMargin * 2; height = viewport.height - bottom + this._viewportMargin; } else { // If neither top nor bottom, it means that the overlay is vertically centered on the // origin point. Note that we want the position relative to the viewport, rather than // the page, which is why we don't use something like `viewport.bottom - origin.y` and // `origin.y - viewport.top`. /** @type {?} */ const smallestDistanceToViewportEdge = Math.min(viewport.bottom - origin.y + viewport.top, origin.y); /** @type {?} */ const previousHeight = this._lastBoundingBoxSize.height; height = smallestDistanceToViewportEdge * 2; top = origin.y - smallestDistanceToViewportEdge; if (height > previousHeight && !this._isInitialRender && !this._growAfterOpen) { top = origin.y - (previousHeight / 2); } } // The overlay is opening 'right-ward' (the content flows to the right). /** @type {?} */ const isBoundedByRightViewportEdge = (position.overlayX === 'start' && !isRtl) || (position.overlayX === 'end' && isRtl); // The overlay is opening 'left-ward' (the content flows to the left). /** @type {?} */ const isBoundedByLeftViewportEdge = (position.overlayX === 'end' && !isRtl) || (position.overlayX === 'start' && isRtl); /** @type {?} */ let width; /** @type {?} */ let left; /** @type {?} */ let right; if (isBoundedByLeftViewportEdge) { right = viewport.width - origin.x + this._viewportMargin; width = origin.x - this._viewportMargin; } else if (isBoundedByRightViewportEdge) { left = origin.x; width = viewport.right - origin.x; } else { // If neither start nor end, it means that the overlay is horizontally centered on the // origin point. Note that we want the position relative to the viewport, rather than // the page, which is why we don't use something like `viewport.right - origin.x` and // `origin.x - viewport.left`. /** @type {?} */ const smallestDistanceToViewportEdge = Math.min(viewport.right - origin.x + viewport.left, origin.x); /** @type {?} */ const previousWidth = this._lastBoundingBoxSize.width; width = smallestDistanceToViewportEdge * 2; left = origin.x - smallestDistanceToViewportEdge; if (width > previousWidth && !this._isInitialRender && !this._growAfterOpen) { left = origin.x - (previousWidth / 2); } } return { top: (/** @type {?} */ (top)), left: (/** @type {?} */ (left)), bottom: (/** @type {?} */ (bottom)), right: (/** @type {?} */ (right)), width, height }; } /** * Sets the position and size of the overlay's sizing wrapper. The wrapper is positioned on the * origin's connection point and stetches to the bounds of the viewport. * * @private * @param {?} origin The point on the origin element where the overlay is connected. * @param {?} position The position preference * @return {?} */ _setBoundingBoxStyles(origin, position) { /** @type {?} */ const boundingBoxRect = this._calculateBoundingBoxRect(origin, position); // It's weird if the overlay *grows* while scrolling, so we take the last size into account // when applying a new size. if (!this._isInitialRender && !this._growAfterOpen) { boundingBoxRect.height = Math.min(boundingBoxRect.height, this._lastBoundingBoxSize.height); boundingBoxRect.width = Math.min(boundingBoxRect.width, this._lastBoundingBoxSize.width); } /** @type {?} */ const styles = (/** @type {?} */ ({})); if (this._hasExactPosition()) { styles.top = styles.left = '0'; styles.bottom = styles.right = styles.maxHeight = styles.maxWidth = ''; styles.width = styles.height = '100%'; } else { /** @type {?} */ const maxHeight = this._overlayRef.getConfig().maxHeight; /** @type {?} */ const maxWidth = this._overlayRef.getConfig().maxWidth; styles.height = coerceCssPixelValue(boundingBoxRect.height); styles.top = coerceCssPixelValue(boundingBoxRect.top); styles.bottom = coerceCssPixelValue(boundingBoxRect.bottom); styles.width = coerceCssPixelValue(boundingBoxRect.width); styles.left = coerceCssPixelValue(boundingBoxRect.left); styles.right = coerceCssPixelValue(boundingBoxRect.right); // Push the pane content towards the proper direction. if (position.overlayX === 'center') { styles.alignItems = 'center'; } else { styles.alignItems = position.overlayX === 'end' ? 'flex-end' : 'flex-start'; } if (position.overlayY === 'center') { styles.justifyContent = 'center'; } else { styles.justifyContent = position.overlayY === 'bottom' ? 'flex-end' : 'flex-start'; } if (maxHeight) { styles.maxHeight = coerceCssPixelValue(maxHeight); } if (maxWidth) { styles.maxWidth = coerceCssPixelValue(maxWidth); } } this._lastBoundingBoxSize = boundingBoxRect; extendStyles((/** @type {?} */ (this._boundingBox)).style, styles); } /** * Resets the styles for the bounding box so that a new positioning can be computed. * @private * @return {?} */ _resetBoundingBoxStyles() { extendStyles((/** @type {?} */ (this._boundingBox)).style, (/** @type {?} */ ({ top: '0', left: '0', right: '0', bottom: '0', height: '', width: '', alignItems: '', justifyContent: '', }))); } /** * Resets the styles for the overlay pane so that a new positioning can be computed. * @private * @return {?} */ _resetOverlayElementStyles() { extendStyles(this._pane.style, (/** @type {?} */ ({ top: '', left: '', bottom: '', right: '', position: '', transform: '', }))); } /** * Sets positioning styles to the overlay element. * @private * @param {?} originPoint * @param {?} position * @return {?} */ _setOverlayElementStyles(originPoint, position) { /** @type {?} */ const styles = (/** @type {?} */ ({})); /** @type {?} */ const hasExactPosition = this._hasExactPosition(); /** @type {?} */ const hasFlexibleDimensions = this._hasFlexibleDimensions; /** @type {?} */ const config = this._overlayRef.getConfig(); if (hasExactPosition) { /** @type {?} */ const scrollPosition = this._viewportRuler.getViewportScrollPosition(); extendStyles(styles, this._getExactOverlayY(position, originPoint, scrollPosition)); extendStyles(styles, this._getExactOverlayX(position, originPoint, scrollPosition)); } else { styles.position = 'static'; } // Use a transform to apply the offsets. We do this because the `center` positions rely on // being in the normal flex flow and setting a `top` / `left` at all will completely throw // off the position. We also can't use margins, because they won't have an effect in some // cases where the element doesn't have anything to "push off of". Finally, this works // better both with flexible and non-flexible positioning. /** @type {?} */ let transformString = ''; /** @type {?} */ let offsetX = this._getOffset(position, 'x'); /** @type {?} */ let offsetY = this._getOffset(position, 'y'); if (offsetX) { transformString += `translateX(${offsetX}px) `; } if (offsetY) { transformString += `translateY(${offsetY}px)`; } styles.transform = transformString.trim(); // If a maxWidth or maxHeight is specified on the overlay, we remove them. We do this because // we need these values to both be set to "100%" for the automatic flexible sizing to work. // The maxHeight and maxWidth are set on the boundingBox in order to enforce the constraint. // Note that this doesn't apply when we have an exact position, in which case we do want to // apply them because they'll be cleared from the bounding box. if (config.maxHeight) { if (hasExactPosition) { styles.maxHeight = coerceCssPixelValue(config.maxHeight); } else if (hasFlexibleDimensions) { styles.maxHeight = ''; } } if (config.maxWidth) { if (hasExactPosition) { styles.maxWidth = coerceCssPixelValue(config.maxWidth); } else if (hasFlexibleDimensions) { styles.maxWidth = ''; } } extendStyles(this._pane.style, styles); } /** * Gets the exact top/bottom for the overlay when not using flexible sizing or when pushing. * @private * @param {?} position * @param {?} originPoint * @param {?} scrollPosition * @return {?} */ _getExactOverlayY(position, originPoint, scrollPosition) { // Reset any existing styles. This is necessary in case the // preferred position has changed since the last `apply`. /** @type {?} */ let styles = (/** @type {?} */ ({ top: '', bottom: '' })); /** @type {?} */ let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position); if (this._isPushed) { overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect, scrollPosition); } /** @type {?} */ let virtualKeyboardOffset = this._overlayContainer.getContainerElement().getBoundingClientRect().top; // Normally this would be zero, however when the overlay is attached to an input (e.g. in an // autocomplete), mobile browsers will shift everything in order to put the input in the middle // of the screen and to make space for the virtual keyboard. We need to account for this offset, // otherwise our positioning will be thrown off. overlayPoint.y -= virtualKeyboardOffset; // We want to set either `top` or `bottom` based on whether the overlay wants to appear // above or below the origin and the direction in which the element will expand. if (position.overlayY === 'bottom') { // When using `bottom`, we adjust the y position such that it is the distance // from the bottom of the viewport rather than the top. /** @type {?} */ const documentHeight = (/** @type {?} */ (this._document.documentElement)).clientHeight; styles.bottom = `${documentHeight - (overlayPoint.y + this._overlayRect.height)}px`; } else { styles.top = coerceCssPixelValue(overlayPoint.y); } return styles; } /** * Gets the exact left/right for the overlay when not using flexible sizing or when pushing. * @private * @param {?} position * @param {?} originPoint * @param {?} scrollPosition * @return {?} */ _getExactOverlayX(position, originPoint, scrollPosition) { // Reset any existing styles. This is necessary in case the preferred position has // changed since the last `apply`. /** @type {?} */ let styles = (/** @type {?} */ ({ left: '', right: '' })); /** @type {?} */ let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position); if (this._isPushed) { overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect, scrollPosition); } // We want to set either `left` or `right` based on whether the overlay wants to appear "before" // or "after" the origin, which determines the direction in which the element will expand. // For the horizontal axis, the meaning of "before" and "after" change based on whether the // page is in RTL or LTR. /** @type {?} */ let horizontalStyleProperty; if (this._isRtl()) { horizontalStyleProperty = position.overlayX === 'end' ? 'left' : 'right'; } else { horizontalStyleProperty = position.overlayX === 'end' ? 'right' : 'left'; } // When we're setting `right`, we adjust the x position such that it is the distance // from the right edge of the viewport rather than the left edge. if (horizontalStyleProperty === 'right') { /** @type {?} */ const documentWidth = (/** @type {?} */ (this._document.documentElement)).clientWidth; styles.right = `${documentWidth - (overlayPoint.x + this._overlayRect.width)}px`; } else { styles.left = coerceCssPixelValue(overlayPoint.x); } return styles; } /** * Gets the view properties of the trigger and overlay, including whether they are clipped * or completely outside the view of any of the strategy's scrollables. * @private * @return {?} */ _getScrollVisibility() { // Note: needs fresh rects since the position could've changed. /** @type {?} */ const originBounds = this._getOriginRect(); /** @type {?} */ const overlayBounds = this._pane.getBoundingClientRect(); // TODO(jelbourn): instead of needing all of the client rects for these scrolling containers // every time, we should be able to use the scrollTop of the containers if the size of those // containers hasn't changed. /** @type {?} */ const scrollContainerBounds = this._scrollables.map((/** * @param {?} scrollable * @return {?} */ scrollable => { return scrollable.getElementRef().nativeElement.getBoundingClientRect(); })); return { isOriginClipped: isElementClippedByScrolling(originBounds, scrollContainerBounds), isOriginOutsideView: isElementScrolledOutsideView(originBounds, scrollContainerBounds), isOverlayClipped: isElementClippedByScrolling(overlayBounds, scrollContainerBounds), isOverlayOutsideView: isElementScrolledOutsideView(overlayBounds, scrollContainerBounds), }; } /** * Subtracts the amount that an element is overflowing on an axis from its length. * @private * @param {?} length * @param {...?} overflows * @return {?} */ _subtractOverflows(length, ...overflows) { return overflows.reduce((/** * @param {?} currentValue * @param {?} currentOverflow * @return {?} */ (currentValue, currentOverflow) => { return currentValue - Math.max(currentOverflow, 0); }), length); } /** * Narrows the given viewport rect by the current _viewportMargin. * @private * @return {?} */ _getNarrowedViewportRect() { // We recalculate the viewport rect here ourselves, rather than using the ViewportRuler, // because we want to use the `clientWidth` and `clientHeight` as the base. The difference // being that the client properties don't include the scrollbar, as opposed to `innerWidth` // and `innerHeight` that do. This is necessary, because the overlay container uses // 100% `width` and `height` which don't include the scrollbar either. /** @type {?} */ const width = (/** @type {?} */ (this._document.documentElement)).clientWidth; /** @type {?} */ const height = (/** @type {?} */ (this._document.documentElement)).clientHeight; /** @type {?} */ const scrollPosition = this._viewportRuler.getViewportScrollPosition(); return { top: scrollPosition.top + this._viewportMargin, left: scrollPosition.left + this._viewportMargin, right: scrollPosition.left + width - this._viewportMargin, bottom: scrollPosition.top + height - this._viewportMargin, width: width - (2 * this._viewportMargin), height: height - (2 * this._viewportMargin), }; } /** * Whether the we're dealing with an RTL context * @private * @return {?} */ _isRtl() { return this._overlayRef.getDirection() === 'rtl'; } /** * Determines whether the overlay uses exact or flexible positioning. * @private * @return {?} */ _hasExactPosition() { return !this._hasFlexibleDimensions || this._isPushed; } /** * Retrieves the offset of a position along the x or y axis. * @private * @param {?} position * @param {?} axis * @return {?} */ _getOffset(position, axis) { if (axis === 'x') { // We don't do something like `position['offset' + axis]` in // order to avoid breking minifiers that rename properties. return position.offsetX == null ? this._offsetX : position.offsetX; } return position.offsetY == null ? this._offsetY : position.offsetY; } /** * Validates that the current position match the expected values. * @private * @return {?} */ _validatePositions() { if (!this._preferredPositions.length) { throw Error('FlexibleConnectedPositionStrategy: At least one position is required.'); } // TODO(crisbeto): remove these once Angular's template type // checking is advanced enough to catch these cases. this._preferredPositions.forEach((/** * @param {?} pair * @return {?} */ pair => { validateHorizontalPosition('originX', pair.originX); validateVerticalPosition('originY', pair.originY); validateHorizontalPosition('overlayX', pair.overlayX); validateVerticalPosition('overlayY', pair.overlayY); })); } /** * Adds a single CSS class or an array of classes on the overlay panel. * @private * @param {?} cssClasses * @return {?} */ _addPanelClasses(cssClasses) { if (this._pane) { coerceArray(cssClasses).forEach((/** * @param {?} cssClass * @return {?} */ cssClass => { if (cssClass !== '' && this._appliedPanelClasses.indexOf(cssClass) === -1) { this._appliedPanelClasses.push(cssClass); this._pane.classList.add(cssClass); } })); } } /** * Clears the classes that the position strategy has applied from the overlay panel. * @private * @return {?} */ _clearPanelClasses() { if (this._pane) { this._appliedPanelClasses.forEach((/** * @param {?} cssClass * @return {?} */ cssClass => { this._pane.classList.remove(cssClass); })); this._appliedPanelClasses = []; } } /** * Returns the ClientRect of the current origin. * @private * @return {?} */ _getOriginRect() { /** @type {?} */ const origin = this._origin; if (origin instanceof ElementRef) { return origin.nativeElement.getBoundingClientRect(); } if (origin instanceof HTMLElement) { return origin.getBoundingClientRect(); } /** @type {?} */ const width = origin.width || 0; /** @type {?} */ const height = origin.height || 0; // If the origin is a point, return a client rect as if it was a 0x0 element at the point. return { top: origin.y, bottom: origin.y + height, left: origin.x, right: origin.x + width, height, width }; } } if (false) { /** * The overlay to which this strategy is attached. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._overlayRef; /** * Whether we're performing the very first positioning of the overlay. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._isInitialRender; /** * Last size used for the bounding box. Used to avoid resizing the overlay after open. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._lastBoundingBoxSize; /** * Whether the overlay was pushed in a previous positioning. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._isPushed; /** * Whether the overlay can be pushed on-screen on the initial open. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._canPush; /** * Whether the overlay can grow via flexible width/height after the initial open. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._growAfterOpen; /** * Whether the overlay's width and height can be constrained to fit within the viewport. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._hasFlexibleDimensions; /** * Whether the overlay position is locked. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._positionLocked; /** * Cached origin dimensions * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._originRect; /** * Cached overlay dimensions * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._overlayRect; /** * Cached viewport dimensions * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._viewportRect; /** * Amount of space that must be maintained between the overlay and the edge of the viewport. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._viewportMargin; /** * The Scrollable containers used to check scrollable view properties on position change. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._scrollables; /** * Ordered list of preferred positions, from most to least desirable. * @type {?} */ FlexibleConnectedPositionStrategy.prototype._preferredPositions; /** * The origin element against which the overlay will be positioned. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._origin; /** * The overlay pane element. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._pane; /** * Whether the strategy has been disposed of already. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._isDisposed; /** * Parent element for the overlay panel used to constrain the overlay panel's size to fit * within the viewport. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._boundingBox; /** * The last position to have been calculated as the best fit position. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._lastPosition; /** * Subject that emits whenever the position changes. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._positionChanges; /** * Subscription to viewport size changes. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._resizeSubscription; /** * Default offset for the overlay along the x axis. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._offsetX; /** * Default offset for the overlay along the y axis. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._offsetY; /** * Selector to be used when finding the elements on which to set the transform origin. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._transformOriginSelector; /** * Keeps track of the CSS classes that the position strategy has applied on the overlay panel. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._appliedPanelClasses; /** * Amount by which the overlay was pushed in each axis during the last time it was positioned. * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._previousPushAmount; /** * Observable sequence of position changes. * @type {?} */ FlexibleConnectedPositionStrategy.prototype.positionChanges; /** * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._viewportRuler; /** * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._document; /** * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._platform; /** * @type {?} * @private */ FlexibleConnectedPositionStrategy.prototype._overlayContainer; } /** * A simple (x, y) coordinate. * @record */ function Point() { } if (false) { /** @type {?} */ Point.prototype.x; /** @type {?} */ Point.prototype.y; } /** * Record of measurements for how an overlay (at a given position) fits into the viewport. * @record */ function OverlayFit() { } if (false) { /** * Whether the overlay fits completely in the viewport. * @type {?} */ OverlayFit.prototype.isCompletelyWithinViewport; /** * Whether the overlay fits in the viewport on the y-axis. * @type {?} */ OverlayFit.prototype.fitsInViewportVertically; /** * Whether the overlay fits in the viewport on the x-axis. * @type {?} */ OverlayFit.prototype.fitsInViewportHorizontally; /** * The total visible area (in px^2) of the overlay inside the viewport. * @type {?} */ OverlayFit.prototype.visibleArea; } /** * Record of the measurments determining whether an overlay will fit in a specific position. * @record */ function FallbackPosition() { } if (false) { /** @type {?} */ FallbackPosition.prototype.position; /** @type {?} */ FallbackPosition.prototype.originPoint; /** @type {?} */ FallbackPosition.prototype.overlayPoint; /** @type {?} */ FallbackPosition.prototype.overlayFit; /** @type {?} */ FallbackPosition.prototype.overlayRect; } /** * Position and size of the overlay sizing wrapper for a specific position. * @record */ function BoundingBoxRect() { } if (false) { /** @type {?} */ BoundingBoxRect.prototype.top; /** @type {?} */ BoundingBoxRect.prototype.left; /** @type {?} */ BoundingBoxRect.prototype.bottom; /** @type {?} */ BoundingBoxRect.prototype.right; /** @type {?} */ BoundingBoxRect.prototype.height; /** @type {?} */ BoundingBoxRect.prototype.width; } /** * Record of measures determining how well a given position will fit with flexible dimensions. * @record */ function FlexibleFit() { } if (false) { /** @type {?} */ FlexibleFit.prototype.position; /** @type {?} */ FlexibleFit.prototype.origin; /** @type {?} */ FlexibleFit.prototype.overlayRect; /** @type {?} */ FlexibleFit.prototype.boundingBoxRect; } /** * A connected position as specified by the user. * @record */ function ConnectedPosition() { } if (false) { /** @type {?} */ ConnectedPosition.prototype.originX; /** @type {?} */ ConnectedPosition.prototype.originY; /** @type {?} */ ConnectedPosition.prototype.overlayX; /** @type {?} */ ConnectedPosition.prototype.overlayY; /** @type {?|undefined} */ ConnectedPosition.prototype.weight; /** @type {?|undefined} */ ConnectedPosition.prototype.offsetX; /** @type {?|undefined} */ ConnectedPosition.prototype.offsetY; /** @type {?|undefined} */ ConnectedPosition.prototype.panelClass; } /** * Shallow-extends a stylesheet object with another stylesheet object. * @param {?} destination * @param {?} source * @return {?} */ function extendStyles(destination, source) { for (let key in source) { if (source.hasOwnProperty(key)) { destination[key] = source[key]; } } return destination; } /** * Extracts the pixel value as a number from a value, if it's a number * or a CSS pixel string (e.g. `1337px`). Otherwise returns null. * @param {?} input * @return {?} */ function getPixelValue(input) { if (typeof input !== 'number' && input != null) { const [value, units] = input.split(cssUnitPattern); return (!units || units === 'px') ? parseFloat(value) : null; } return input || null; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/position/connected-position-strategy.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * A strategy for positioning overlays. Using this strategy, an overlay is given an * implicit position relative to some origin element. The relative position is defined in terms of * a point on the origin element that is connected to a point on the overlay element. For example, * a basic dropdown is connecting the bottom-left corner of the origin to the top-left corner * of the overlay. * @deprecated Use `FlexibleConnectedPositionStrategy` instead. * \@breaking-change 8.0.0 */ class ConnectedPositionStrategy { /** * @param {?} originPos * @param {?} overlayPos * @param {?} connectedTo * @param {?} viewportRuler * @param {?} document * @param {?} platform * @param {?} overlayContainer */ constructor(originPos, overlayPos, connectedTo, viewportRuler, document, platform, overlayContainer) { /** * Ordered list of preferred positions, from most to least desirable. */ this._preferredPositions = []; // Since the `ConnectedPositionStrategy` is deprecated and we don't want to maintain // the extra logic, we create an instance of the positioning strategy that has some // defaults that make it behave as the old position strategy and to which we'll // proxy all of the API calls. this._positionStrategy = new FlexibleConnectedPositionStrategy(connectedTo, viewportRuler, document, platform, overlayContainer) .withFlexibleDimensions(false) .withPush(false) .withViewportMargin(0); this.withFallbackPosition(originPos, overlayPos); } /** * Whether the we're dealing with an RTL context * @return {?} */ get _isRtl() { return this._overlayRef.getDirection() === 'rtl'; } /** * Emits an event when the connection point changes. * @return {?} */ get onPositionChange() { return this._positionStrategy.positionChanges; } /** * Ordered list of preferred positions, from most to least desirable. * @return {?} */ get positions() { return this._preferredPositions; } /** * Attach this position strategy to an overlay. * @param {?} overlayRef * @return {?} */ attach(overlayRef) { this._overlayRef = overlayRef; this._positionStrategy.attach(overlayRef); if (this._direction) { overlayRef.setDirection(this._direction); this._direction = null; } } /** * Disposes all resources used by the position strategy. * @return {?} */ dispose() { this._positionStrategy.dispose(); } /** * \@docs-private * @return {?} */ detach() { this._positionStrategy.detach(); } /** * Updates the position of the overlay element, using whichever preferred position relative * to the origin fits on-screen. * \@docs-private * @return {?} */ apply() { this._positionStrategy.apply(); } /** * Re-positions the overlay element with the trigger in its last calculated position, * even if a position higher in the "preferred positions" list would now fit. This * allows one to re-align the panel without changing the orientation of the panel. * @return {?} */ recalculateLastPosition() { this._positionStrategy.reapplyLastPosition(); } /** * Sets the list of Scrollable containers that host the origin element so that * on reposition we can evaluate if it or the overlay has been clipped or outside view. Every * Scrollable must be an ancestor element of the strategy's origin element. * @param {?} scrollables * @return {?} */ withScrollableContainers(scrollables) { this._positionStrategy.withScrollableContainers(scrollables); } /** * Adds a new preferred fallback position. * @template THIS * @this {THIS} * @param {?} originPos * @param {?} overlayPos * @param {?=} offsetX * @param {?=} offsetY * @return {THIS} */ withFallbackPosition(originPos, overlayPos, offsetX, offsetY) { /** @type {?} */ const position = new ConnectionPositionPair(originPos, overlayPos, offsetX, offsetY); (/** @type {?} */ (this))._preferredPositions.push(position); (/** @type {?} */ (this))._positionStrategy.withPositions((/** @type {?} */ (this))._preferredPositions); return (/** @type {?} */ (this)); } /** * Sets the layout direction so the overlay's position can be adjusted to match. * @template THIS * @this {THIS} * @param {?} dir New layout direction. * @return {THIS} */ withDirection(dir) { // Since the direction might be declared before the strategy is attached, // we save the value in a temporary property and we'll transfer it to the // overlay ref on attachment. if ((/** @type {?} */ (this))._overlayRef) { (/** @type {?} */ (this))._overlayRef.setDirection(dir); } else { (/** @type {?} */ (this))._direction = dir; } return (/** @type {?} */ (this)); } /** * Sets an offset for the overlay's connection point on the x-axis * @template THIS * @this {THIS} * @param {?} offset New offset in the X axis. * @return {THIS} */ withOffsetX(offset) { (/** @type {?} */ (this))._positionStrategy.withDefaultOffsetX(offset); return (/** @type {?} */ (this)); } /** * Sets an offset for the overlay's connection point on the y-axis * @template THIS * @this {THIS} * @param {?} offset New offset in the Y axis. * @return {THIS} */ withOffsetY(offset) { (/** @type {?} */ (this))._positionStrategy.withDefaultOffsetY(offset); return (/** @type {?} */ (this)); } /** * Sets whether the overlay's position should be locked in after it is positioned * initially. When an overlay is locked in, it won't attempt to reposition itself * when the position is re-applied (e.g. when the user scrolls away). * @template THIS * @this {THIS} * @param {?} isLocked Whether the overlay should locked in. * @return {THIS} */ withLockedPosition(isLocked) { (/** @type {?} */ (this))._positionStrategy.withLockedPosition(isLocked); return (/** @type {?} */ (this)); } /** * Overwrites the current set of positions with an array of new ones. * @template THIS * @this {THIS} * @param {?} positions Position pairs to be set on the strategy. * @return {THIS} */ withPositions(positions) { (/** @type {?} */ (this))._preferredPositions = positions.slice(); (/** @type {?} */ (this))._positionStrategy.withPositions((/** @type {?} */ (this))._preferredPositions); return (/** @type {?} */ (this)); } /** * Sets the origin element, relative to which to position the overlay. * @template THIS * @this {THIS} * @param {?} origin Reference to the new origin element. * @return {THIS} */ setOrigin(origin) { (/** @type {?} */ (this))._positionStrategy.setOrigin(origin); return (/** @type {?} */ (this)); } } if (false) { /** * Reference to the underlying position strategy to which all the API calls are proxied. * \@docs-private * @type {?} */ ConnectedPositionStrategy.prototype._positionStrategy; /** * The overlay to which this strategy is attached. * @type {?} * @private */ ConnectedPositionStrategy.prototype._overlayRef; /** * @type {?} * @private */ ConnectedPositionStrategy.prototype._direction; /** * Ordered list of preferred positions, from most to least desirable. * @type {?} */ ConnectedPositionStrategy.prototype._preferredPositions; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/position/global-position-strategy.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @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 */ /** * Class to be added to the overlay pane wrapper. * @type {?} */ const wrapperClass = 'cdk-global-overlay-wrapper'; /** * A strategy for positioning overlays. Using this strategy, an overlay is given an * explicit position relative to the browser's viewport. We use flexbox, instead of * transforms, in order to avoid issues with subpixel rendering which can cause the * element to become blurry. */ class GlobalPositionStrategy { constructor() { this._cssPosition = 'static'; this._topOffset = ''; this._bottomOffset = ''; this._leftOffset = ''; this._rightOffset = ''; this._alignItems = ''; this._justifyContent = ''; this._width = ''; this._height = ''; } /** * @param {?} overlayRef * @return {?} */ attach(overlayRef) { /** @type {?} */ const config = overlayRef.getConfig(); this._overlayRef = overlayRef; if (this._width && !config.width) { overlayRef.updateSize({ width: this._width }); } if (this._height && !config.height) { overlayRef.updateSize({ height: this._height }); } overlayRef.hostElement.classList.add(wrapperClass); this._isDisposed = false; } /** * Sets the top position of the overlay. Clears any previously set vertical position. * @template THIS * @this {THIS} * @param {?=} value New top offset. * @return {THIS} */ top(value = '') { (/** @type {?} */ (this))._bottomOffset = ''; (/** @type {?} */ (this))._topOffset = value; (/** @type {?} */ (this))._alignItems = 'flex-start'; return (/** @type {?} */ (this)); } /** * Sets the left position of the overlay. Clears any previously set horizontal position. * @template THIS * @this {THIS} * @param {?=} value New left offset. * @return {THIS} */ left(value = '') { (/** @type {?} */ (this))._rightOffset = ''; (/** @type {?} */ (this))._leftOffset = value; (/** @type {?} */ (this))._justifyContent = 'flex-start'; return (/** @type {?} */ (this)); } /** * Sets the bottom position of the overlay. Clears any previously set vertical position. * @template THIS * @this {THIS} * @param {?=} value New bottom offset. * @return {THIS} */ bottom(value = '') { (/** @type {?} */ (this))._topOffset = ''; (/** @type {?} */ (this))._bottomOffset = value; (/** @type {?} */ (this))._alignItems = 'flex-end'; return (/** @type {?} */ (this)); } /** * Sets the right position of the overlay. Clears any previously set horizontal position. * @template THIS * @this {THIS} * @param {?=} value New right offset. * @return {THIS} */ right(value = '') { (/** @type {?} */ (this))._leftOffset = ''; (/** @type {?} */ (this))._rightOffset = value; (/** @type {?} */ (this))._justifyContent = 'flex-end'; return (/** @type {?} */ (this)); } /** * Sets the overlay width and clears any previously set width. * @deprecated Pass the `width` through the `OverlayConfig`. * \@breaking-change 8.0.0 * @template THIS * @this {THIS} * @param {?=} value New width for the overlay * @return {THIS} */ width(value = '') { if ((/** @type {?} */ (this))._overlayRef) { (/** @type {?} */ (this))._overlayRef.updateSize({ width: value }); } else { (/** @type {?} */ (this))._width = value; } return (/** @type {?} */ (this)); } /** * Sets the overlay height and clears any previously set height. * @deprecated Pass the `height` through the `OverlayConfig`. * \@breaking-change 8.0.0 * @template THIS * @this {THIS} * @param {?=} value New height for the overlay * @return {THIS} */ height(value = '') { if ((/** @type {?} */ (this))._overlayRef) { (/** @type {?} */ (this))._overlayRef.updateSize({ height: value }); } else { (/** @type {?} */ (this))._height = value; } return (/** @type {?} */ (this)); } /** * Centers the overlay horizontally with an optional offset. * Clears any previously set horizontal position. * * @template THIS * @this {THIS} * @param {?=} offset Overlay offset from the horizontal center. * @return {THIS} */ centerHorizontally(offset = '') { (/** @type {?} */ (this)).left(offset); (/** @type {?} */ (this))._justifyContent = 'center'; return (/** @type {?} */ (this)); } /** * Centers the overlay vertically with an optional offset. * Clears any previously set vertical position. * * @template THIS * @this {THIS} * @param {?=} offset Overlay offset from the vertical center. * @return {THIS} */ centerVertically(offset = '') { (/** @type {?} */ (this)).top(offset); (/** @type {?} */ (this))._alignItems = 'center'; return (/** @type {?} */ (this)); } /** * Apply the position to the element. * \@docs-private * @return {?} */ apply() { // Since the overlay ref applies the strategy asynchronously, it could // have been disposed before it ends up being applied. If that is the // case, we shouldn't do anything. if (!this._overlayRef || !this._overlayRef.hasAttached()) { return; } /** @type {?} */ const styles = this._overlayRef.overlayElement.style; /** @type {?} */ const parentStyles = this._overlayRef.hostElement.style; /** @type {?} */ const config = this._overlayRef.getConfig(); const { width, height, maxWidth, maxHeight } = config; /** @type {?} */ const shouldBeFlushHorizontally = (width === '100%' || width === '100vw') && (!maxWidth || maxWidth === '100%' || maxWidth === '100vw'); /** @type {?} */ const shouldBeFlushVertically = (height === '100%' || height === '100vh') && (!maxHeight || maxHeight === '100%' || maxHeight === '100vh'); styles.position = this._cssPosition; styles.marginLeft = shouldBeFlushHorizontally ? '0' : this._leftOffset; styles.marginTop = shouldBeFlushVertically ? '0' : this._topOffset; styles.marginBottom = this._bottomOffset; styles.marginRight = this._rightOffset; if (shouldBeFlushHorizontally) { parentStyles.justifyContent = 'flex-start'; } else if (this._justifyContent === 'center') { parentStyles.justifyContent = 'center'; } else if (this._overlayRef.getConfig().direction === 'rtl') { // In RTL the browser will invert `flex-start` and `flex-end` automatically, but we // don't want that because our positioning is explicitly `left` and `right`, hence // why we do another inversion to ensure that the overlay stays in the same position. // TODO: reconsider this if we add `start` and `end` methods. if (this._justifyContent === 'flex-start') { parentStyles.justifyContent = 'flex-end'; } else if (this._justifyContent === 'flex-end') { parentStyles.justifyContent = 'flex-start'; } } else { parentStyles.justifyContent = this._justifyContent; } parentStyles.alignItems = shouldBeFlushVertically ? 'flex-start' : this._alignItems; } /** * Cleans up the DOM changes from the position strategy. * \@docs-private * @return {?} */ dispose() { if (this._isDisposed || !this._overlayRef) { return; } /** @type {?} */ const styles = this._overlayRef.overlayElement.style; /** @type {?} */ const parent = this._overlayRef.hostElement; /** @type {?} */ const parentStyles = parent.style; parent.classList.remove(wrapperClass); parentStyles.justifyContent = parentStyles.alignItems = styles.marginTop = styles.marginBottom = styles.marginLeft = styles.marginRight = styles.position = ''; this._overlayRef = (/** @type {?} */ (null)); this._isDisposed = true; } } if (false) { /** * The overlay to which this strategy is attached. * @type {?} * @private */ GlobalPositionStrategy.prototype._overlayRef; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._cssPosition; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._topOffset; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._bottomOffset; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._leftOffset; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._rightOffset; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._alignItems; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._justifyContent; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._width; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._height; /** * @type {?} * @private */ GlobalPositionStrategy.prototype._isDisposed; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/position/overlay-position-builder.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Builder for overlay position strategy. */ class OverlayPositionBuilder { /** * @param {?} _viewportRuler * @param {?} _document * @param {?} _platform * @param {?} _overlayContainer */ constructor(_viewportRuler, _document, _platform, _overlayContainer) { this._viewportRuler = _viewportRuler; this._document = _document; this._platform = _platform; this._overlayContainer = _overlayContainer; } /** * Creates a global position strategy. * @return {?} */ global() { return new GlobalPositionStrategy(); } /** * Creates a relative position strategy. * @deprecated Use `flexibleConnectedTo` instead. * \@breaking-change 8.0.0 * @param {?} elementRef * @param {?} originPos * @param {?} overlayPos * @return {?} */ connectedTo(elementRef, originPos, overlayPos) { return new ConnectedPositionStrategy(originPos, overlayPos, elementRef, this._viewportRuler, this._document, this._platform, this._overlayContainer); } /** * Creates a flexible position strategy. * @param {?} origin Origin relative to which to position the overlay. * @return {?} */ flexibleConnectedTo(origin) { return new FlexibleConnectedPositionStrategy(origin, this._viewportRuler, this._document, this._platform, this._overlayContainer); } } OverlayPositionBuilder.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; /** @nocollapse */ OverlayPositionBuilder.ctorParameters = () => [ { type: ViewportRuler }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: Platform }, { type: OverlayContainer } ]; /** @nocollapse */ OverlayPositionBuilder.ɵprov = ɵɵdefineInjectable({ factory: function OverlayPositionBuilder_Factory() { return new OverlayPositionBuilder(ɵɵinject(ViewportRuler), ɵɵinject(DOCUMENT), ɵɵinject(Platform), ɵɵinject(OverlayContainer)); }, token: OverlayPositionBuilder, providedIn: "root" }); if (false) { /** * @type {?} * @private */ OverlayPositionBuilder.prototype._viewportRuler; /** * @type {?} * @private */ OverlayPositionBuilder.prototype._document; /** * @type {?} * @private */ OverlayPositionBuilder.prototype._platform; /** * @type {?} * @private */ OverlayPositionBuilder.prototype._overlayContainer; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/overlay.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Next overlay unique ID. * @type {?} */ let nextUniqueId = 0; // Note that Overlay is *not* scoped to the app root because of the ComponentFactoryResolver // which needs to be different depending on where OverlayModule is imported. /** * Service to create Overlays. Overlays are dynamically added pieces of floating UI, meant to be * used as a low-level building block for other components. Dialogs, tooltips, menus, * selects, etc. can all be built using overlays. The service should primarily be used by authors * of re-usable components rather than developers building end-user applications. * * An overlay *is* a PortalOutlet, so any kind of Portal can be loaded into one. */ class Overlay { /** * @param {?} scrollStrategies * @param {?} _overlayContainer * @param {?} _componentFactoryResolver * @param {?} _positionBuilder * @param {?} _keyboardDispatcher * @param {?} _injector * @param {?} _ngZone * @param {?} _document * @param {?} _directionality * @param {?=} _location */ constructor(scrollStrategies, _overlayContainer, _componentFactoryResolver, _positionBuilder, _keyboardDispatcher, _injector, _ngZone, _document, _directionality, _location) { this.scrollStrategies = scrollStrategies; this._overlayContainer = _overlayContainer; this._componentFactoryResolver = _componentFactoryResolver; this._positionBuilder = _positionBuilder; this._keyboardDispatcher = _keyboardDispatcher; this._injector = _injector; this._ngZone = _ngZone; this._document = _document; this._directionality = _directionality; this._location = _location; } /** * Creates an overlay. * @param {?=} config Configuration applied to the overlay. * @return {?} Reference to the created overlay. */ create(config) { /** @type {?} */ const host = this._createHostElement(); /** @type {?} */ const pane = this._createPaneElement(host); /** @type {?} */ const portalOutlet = this._createPortalOutlet(pane); /** @type {?} */ const overlayConfig = new OverlayConfig(config); overlayConfig.direction = overlayConfig.direction || this._directionality.value; return new OverlayRef(portalOutlet, host, pane, overlayConfig, this._ngZone, this._keyboardDispatcher, this._document, this._location); } /** * Gets a position builder that can be used, via fluent API, * to construct and configure a position strategy. * @return {?} An overlay position builder. */ position() { return this._positionBuilder; } /** * Creates the DOM element for an overlay and appends it to the overlay container. * @private * @param {?} host * @return {?} Newly-created pane element */ _createPaneElement(host) { /** @type {?} */ const pane = this._document.createElement('div'); pane.id = `cdk-overlay-${nextUniqueId++}`; pane.classList.add('cdk-overlay-pane'); host.appendChild(pane); return pane; } /** * Creates the host element that wraps around an overlay * and can be used for advanced positioning. * @private * @return {?} Newly-create host element. */ _createHostElement() { /** @type {?} */ const host = this._document.createElement('div'); this._overlayContainer.getContainerElement().appendChild(host); return host; } /** * Create a DomPortalOutlet into which the overlay content can be loaded. * @private * @param {?} pane The DOM element to turn into a portal outlet. * @return {?} A portal outlet for the given DOM element. */ _createPortalOutlet(pane) { // We have to resolve the ApplicationRef later in order to allow people // to use overlay-based providers during app initialization. if (!this._appRef) { this._appRef = this._injector.get(ApplicationRef); } return new DomPortalOutlet(pane, this._componentFactoryResolver, this._appRef, this._injector, this._document); } } Overlay.decorators = [ { type: Injectable } ]; /** @nocollapse */ Overlay.ctorParameters = () => [ { type: ScrollStrategyOptions }, { type: OverlayContainer }, { type: ComponentFactoryResolver }, { type: OverlayPositionBuilder }, { type: OverlayKeyboardDispatcher }, { type: Injector }, { type: NgZone }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: Directionality }, { type: Location, decorators: [{ type: Optional }] } ]; if (false) { /** * @type {?} * @private */ Overlay.prototype._appRef; /** * Scrolling strategies that can be used when creating an overlay. * @type {?} */ Overlay.prototype.scrollStrategies; /** * @type {?} * @private */ Overlay.prototype._overlayContainer; /** * @type {?} * @private */ Overlay.prototype._componentFactoryResolver; /** * @type {?} * @private */ Overlay.prototype._positionBuilder; /** * @type {?} * @private */ Overlay.prototype._keyboardDispatcher; /** * @type {?} * @private */ Overlay.prototype._injector; /** * @type {?} * @private */ Overlay.prototype._ngZone; /** * @type {?} * @private */ Overlay.prototype._document; /** * @type {?} * @private */ Overlay.prototype._directionality; /** * @type {?} * @private */ Overlay.prototype._location; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/overlay-directives.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Default set of positions for the overlay. Follows the behavior of a dropdown. * @type {?} */ const defaultPositionList = [ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' }, { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' }, { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom' }, { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top' } ]; /** * Injection token that determines the scroll handling while the connected overlay is open. * @type {?} */ const CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY = new InjectionToken('cdk-connected-overlay-scroll-strategy'); /** * \@docs-private \@deprecated \@breaking-change 8.0.0 * @param {?} overlay * @return {?} */ function CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_FACTORY(overlay) { return (/** * @param {?=} config * @return {?} */ (config) => overlay.scrollStrategies.reposition(config)); } /** * Directive applied to an element to make it usable as an origin for an Overlay using a * ConnectedPositionStrategy. */ class CdkOverlayOrigin { /** * @param {?} elementRef */ constructor(elementRef) { this.elementRef = elementRef; } } CdkOverlayOrigin.decorators = [ { type: Directive, args: [{ selector: '[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]', exportAs: 'cdkOverlayOrigin', },] } ]; /** @nocollapse */ CdkOverlayOrigin.ctorParameters = () => [ { type: ElementRef } ]; if (false) { /** * Reference to the element on which the directive is applied. * @type {?} */ CdkOverlayOrigin.prototype.elementRef; } /** * Directive to facilitate declarative creation of an * Overlay using a FlexibleConnectedPositionStrategy. */ class CdkConnectedOverlay { // TODO(jelbourn): inputs for size, scroll behavior, animation, etc. /** * @param {?} _overlay * @param {?} templateRef * @param {?} viewContainerRef * @param {?} scrollStrategyFactory * @param {?} _dir */ constructor(_overlay, templateRef, viewContainerRef, scrollStrategyFactory, _dir) { this._overlay = _overlay; this._dir = _dir; this._hasBackdrop = false; this._lockPosition = false; this._growAfterOpen = false; this._flexibleDimensions = false; this._push = false; this._backdropSubscription = Subscription.EMPTY; /** * Margin between the overlay and the viewport edges. */ this.viewportMargin = 0; /** * Whether the overlay is open. */ this.open = false; /** * Event emitted when the backdrop is clicked. */ this.backdropClick = new EventEmitter(); /** * Event emitted when the position has changed. */ this.positionChange = new EventEmitter(); /** * Event emitted when the overlay has been attached. */ this.attach = new EventEmitter(); /** * Event emitted when the overlay has been detached. */ this.detach = new EventEmitter(); /** * Emits when there are keyboard events that are targeted at the overlay. */ this.overlayKeydown = new EventEmitter(); this._templatePortal = new TemplatePortal(templateRef, viewContainerRef); this._scrollStrategyFactory = scrollStrategyFactory; this.scrollStrategy = this._scrollStrategyFactory(); } /** * The offset in pixels for the overlay connection point on the x-axis * @return {?} */ get offsetX() { return this._offsetX; } /** * @param {?} offsetX * @return {?} */ set offsetX(offsetX) { this._offsetX = offsetX; if (this._position) { this._updatePositionStrategy(this._position); } } /** * The offset in pixels for the overlay connection point on the y-axis * @return {?} */ get offsetY() { return this._offsetY; } /** * @param {?} offsetY * @return {?} */ set offsetY(offsetY) { this._offsetY = offsetY; if (this._position) { this._updatePositionStrategy(this._position); } } /** * Whether or not the overlay should attach a backdrop. * @return {?} */ get hasBackdrop() { return this._hasBackdrop; } /** * @param {?} value * @return {?} */ set hasBackdrop(value) { this._hasBackdrop = coerceBooleanProperty(value); } /** * Whether or not the overlay should be locked when scrolling. * @return {?} */ get lockPosition() { return this._lockPosition; } /** * @param {?} value * @return {?} */ set lockPosition(value) { this._lockPosition = coerceBooleanProperty(value); } /** * Whether the overlay's width and height can be constrained to fit within the viewport. * @return {?} */ get flexibleDimensions() { return this._flexibleDimensions; } /** * @param {?} value * @return {?} */ set flexibleDimensions(value) { this._flexibleDimensions = coerceBooleanProperty(value); } /** * Whether the overlay can grow after the initial open when flexible positioning is turned on. * @return {?} */ get growAfterOpen() { return this._growAfterOpen; } /** * @param {?} value * @return {?} */ set growAfterOpen(value) { this._growAfterOpen = coerceBooleanProperty(value); } /** * Whether the overlay can be pushed on-screen if none of the provided positions fit. * @return {?} */ get push() { return this._push; } /** * @param {?} value * @return {?} */ set push(value) { this._push = coerceBooleanProperty(value); } /** * The associated overlay reference. * @return {?} */ get overlayRef() { return this._overlayRef; } /** * The element's layout direction. * @return {?} */ get dir() { return this._dir ? this._dir.value : 'ltr'; } /** * @return {?} */ ngOnDestroy() { if (this._overlayRef) { this._overlayRef.dispose(); } this._backdropSubscription.unsubscribe(); } /** * @param {?} changes * @return {?} */ ngOnChanges(changes) { if (this._position) { this._updatePositionStrategy(this._position); this._overlayRef.updateSize({ width: this.width, minWidth: this.minWidth, height: this.height, minHeight: this.minHeight, }); if (changes['origin'] && this.open) { this._position.apply(); } } if (changes['open']) { this.open ? this._attachOverlay() : this._detachOverlay(); } } /** * Creates an overlay * @private * @return {?} */ _createOverlay() { if (!this.positions || !this.positions.length) { this.positions = defaultPositionList; } this._overlayRef = this._overlay.create(this._buildConfig()); this._overlayRef.keydownEvents().subscribe((/** * @param {?} event * @return {?} */ (event) => { this.overlayKeydown.next(event); if (event.keyCode === ESCAPE && !hasModifierKey(event)) { event.preventDefault(); this._detachOverlay(); } })); } /** * Builds the overlay config based on the directive's inputs * @private * @return {?} */ _buildConfig() { /** @type {?} */ const positionStrategy = this._position = this.positionStrategy || this._createPositionStrategy(); /** @type {?} */ const overlayConfig = new OverlayConfig({ direction: this._dir, positionStrategy, scrollStrategy: this.scrollStrategy, hasBackdrop: this.hasBackdrop }); if (this.width || this.width === 0) { overlayConfig.width = this.width; } if (this.height || this.height === 0) { overlayConfig.height = this.height; } if (this.minWidth || this.minWidth === 0) { overlayConfig.minWidth = this.minWidth; } if (this.minHeight || this.minHeight === 0) { overlayConfig.minHeight = this.minHeight; } if (this.backdropClass) { overlayConfig.backdropClass = this.backdropClass; } if (this.panelClass) { overlayConfig.panelClass = this.panelClass; } return overlayConfig; } /** * Updates the state of a position strategy, based on the values of the directive inputs. * @private * @param {?} positionStrategy * @return {?} */ _updatePositionStrategy(positionStrategy) { /** @type {?} */ const positions = this.positions.map((/** * @param {?} currentPosition * @return {?} */ currentPosition => ({ originX: currentPosition.originX, originY: currentPosition.originY, overlayX: currentPosition.overlayX, overlayY: currentPosition.overlayY, offsetX: currentPosition.offsetX || this.offsetX, offsetY: currentPosition.offsetY || this.offsetY, panelClass: currentPosition.panelClass || undefined, }))); return positionStrategy .setOrigin(this.origin.elementRef) .withPositions(positions) .withFlexibleDimensions(this.flexibleDimensions) .withPush(this.push) .withGrowAfterOpen(this.growAfterOpen) .withViewportMargin(this.viewportMargin) .withLockedPosition(this.lockPosition) .withTransformOriginOn(this.transformOriginSelector); } /** * Returns the position strategy of the overlay to be set on the overlay config * @private * @return {?} */ _createPositionStrategy() { /** @type {?} */ const strategy = this._overlay.position().flexibleConnectedTo(this.origin.elementRef); this._updatePositionStrategy(strategy); strategy.positionChanges.subscribe((/** * @param {?} p * @return {?} */ p => this.positionChange.emit(p))); return strategy; } /** * Attaches the overlay and subscribes to backdrop clicks if backdrop exists * @private * @return {?} */ _attachOverlay() { if (!this._overlayRef) { this._createOverlay(); } else { // Update the overlay size, in case the directive's inputs have changed this._overlayRef.getConfig().hasBackdrop = this.hasBackdrop; } if (!this._overlayRef.hasAttached()) { this._overlayRef.attach(this._templatePortal); this.attach.emit(); } if (this.hasBackdrop) { this._backdropSubscription = this._overlayRef.backdropClick().subscribe((/** * @param {?} event * @return {?} */ event => { this.backdropClick.emit(event); })); } else { this._backdropSubscription.unsubscribe(); } } /** * Detaches the overlay and unsubscribes to backdrop clicks if backdrop exists * @private * @return {?} */ _detachOverlay() { if (this._overlayRef) { this._overlayRef.detach(); this.detach.emit(); } this._backdropSubscription.unsubscribe(); } } CdkConnectedOverlay.decorators = [ { type: Directive, args: [{ selector: '[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]', exportAs: 'cdkConnectedOverlay' },] } ]; /** @nocollapse */ CdkConnectedOverlay.ctorParameters = () => [ { type: Overlay }, { type: TemplateRef }, { type: ViewContainerRef }, { type: undefined, decorators: [{ type: Inject, args: [CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY,] }] }, { type: Directionality, decorators: [{ type: Optional }] } ]; CdkConnectedOverlay.propDecorators = { origin: [{ type: Input, args: ['cdkConnectedOverlayOrigin',] }], positions: [{ type: Input, args: ['cdkConnectedOverlayPositions',] }], positionStrategy: [{ type: Input, args: ['cdkConnectedOverlayPositionStrategy',] }], offsetX: [{ type: Input, args: ['cdkConnectedOverlayOffsetX',] }], offsetY: [{ type: Input, args: ['cdkConnectedOverlayOffsetY',] }], width: [{ type: Input, args: ['cdkConnectedOverlayWidth',] }], height: [{ type: Input, args: ['cdkConnectedOverlayHeight',] }], minWidth: [{ type: Input, args: ['cdkConnectedOverlayMinWidth',] }], minHeight: [{ type: Input, args: ['cdkConnectedOverlayMinHeight',] }], backdropClass: [{ type: Input, args: ['cdkConnectedOverlayBackdropClass',] }], panelClass: [{ type: Input, args: ['cdkConnectedOverlayPanelClass',] }], viewportMargin: [{ type: Input, args: ['cdkConnectedOverlayViewportMargin',] }], scrollStrategy: [{ type: Input, args: ['cdkConnectedOverlayScrollStrategy',] }], open: [{ type: Input, args: ['cdkConnectedOverlayOpen',] }], transformOriginSelector: [{ type: Input, args: ['cdkConnectedOverlayTransformOriginOn',] }], hasBackdrop: [{ type: Input, args: ['cdkConnectedOverlayHasBackdrop',] }], lockPosition: [{ type: Input, args: ['cdkConnectedOverlayLockPosition',] }], flexibleDimensions: [{ type: Input, args: ['cdkConnectedOverlayFlexibleDimensions',] }], growAfterOpen: [{ type: Input, args: ['cdkConnectedOverlayGrowAfterOpen',] }], push: [{ type: Input, args: ['cdkConnectedOverlayPush',] }], backdropClick: [{ type: Output }], positionChange: [{ type: Output }], attach: [{ type: Output }], detach: [{ type: Output }], overlayKeydown: [{ type: Output }] }; if (false) { /** @type {?} */ CdkConnectedOverlay.ngAcceptInputType_hasBackdrop; /** @type {?} */ CdkConnectedOverlay.ngAcceptInputType_lockPosition; /** @type {?} */ CdkConnectedOverlay.ngAcceptInputType_flexibleDimensions; /** @type {?} */ CdkConnectedOverlay.ngAcceptInputType_growAfterOpen; /** @type {?} */ CdkConnectedOverlay.ngAcceptInputType_push; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._overlayRef; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._templatePortal; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._hasBackdrop; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._lockPosition; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._growAfterOpen; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._flexibleDimensions; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._push; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._backdropSubscription; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._offsetX; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._offsetY; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._position; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._scrollStrategyFactory; /** * Origin for the connected overlay. * @type {?} */ CdkConnectedOverlay.prototype.origin; /** * Registered connected position pairs. * @type {?} */ CdkConnectedOverlay.prototype.positions; /** * This input overrides the positions input if specified. It lets users pass * in arbitrary positioning strategies. * @type {?} */ CdkConnectedOverlay.prototype.positionStrategy; /** * The width of the overlay panel. * @type {?} */ CdkConnectedOverlay.prototype.width; /** * The height of the overlay panel. * @type {?} */ CdkConnectedOverlay.prototype.height; /** * The min width of the overlay panel. * @type {?} */ CdkConnectedOverlay.prototype.minWidth; /** * The min height of the overlay panel. * @type {?} */ CdkConnectedOverlay.prototype.minHeight; /** * The custom class to be set on the backdrop element. * @type {?} */ CdkConnectedOverlay.prototype.backdropClass; /** * The custom class to add to the overlay pane element. * @type {?} */ CdkConnectedOverlay.prototype.panelClass; /** * Margin between the overlay and the viewport edges. * @type {?} */ CdkConnectedOverlay.prototype.viewportMargin; /** * Strategy to be used when handling scroll events while the overlay is open. * @type {?} */ CdkConnectedOverlay.prototype.scrollStrategy; /** * Whether the overlay is open. * @type {?} */ CdkConnectedOverlay.prototype.open; /** * CSS selector which to set the transform origin. * @type {?} */ CdkConnectedOverlay.prototype.transformOriginSelector; /** * Event emitted when the backdrop is clicked. * @type {?} */ CdkConnectedOverlay.prototype.backdropClick; /** * Event emitted when the position has changed. * @type {?} */ CdkConnectedOverlay.prototype.positionChange; /** * Event emitted when the overlay has been attached. * @type {?} */ CdkConnectedOverlay.prototype.attach; /** * Event emitted when the overlay has been detached. * @type {?} */ CdkConnectedOverlay.prototype.detach; /** * Emits when there are keyboard events that are targeted at the overlay. * @type {?} */ CdkConnectedOverlay.prototype.overlayKeydown; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._overlay; /** * @type {?} * @private */ CdkConnectedOverlay.prototype._dir; } /** * \@docs-private * @param {?} overlay * @return {?} */ function CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay) { return (/** * @return {?} */ () => overlay.scrollStrategies.reposition()); } /** * \@docs-private * @type {?} */ const CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER = { provide: CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY, deps: [Overlay], useFactory: CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY, }; /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/overlay-module.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class OverlayModule { } OverlayModule.decorators = [ { type: NgModule, args: [{ imports: [BidiModule, PortalModule, ScrollingModule], exports: [CdkConnectedOverlay, CdkOverlayOrigin, ScrollingModule], declarations: [CdkConnectedOverlay, CdkOverlayOrigin], providers: [ Overlay, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER, ], },] } ]; /** * @deprecated Use `OverlayModule` instead. * \@breaking-change 8.0.0 * \@docs-private * @type {?} */ const OVERLAY_PROVIDERS = [ Overlay, OverlayPositionBuilder, OVERLAY_KEYBOARD_DISPATCHER_PROVIDER, OVERLAY_CONTAINER_PROVIDER, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER, ]; /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/fullscreen-overlay-container.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Alternative to OverlayContainer that supports correct displaying of overlay elements in * Fullscreen mode * https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen * * Should be provided in the root component. */ class FullscreenOverlayContainer extends OverlayContainer { /** * @param {?} _document * @param {?=} platform */ constructor(_document, /** * @deprecated `platform` parameter to become required. * @breaking-change 10.0.0 */ platform) { super(_document, platform); } /** * @return {?} */ ngOnDestroy() { super.ngOnDestroy(); if (this._fullScreenEventName && this._fullScreenListener) { this._document.removeEventListener(this._fullScreenEventName, this._fullScreenListener); } } /** * @protected * @return {?} */ _createContainer() { super._createContainer(); this._adjustParentForFullscreenChange(); this._addFullscreenChangeListener((/** * @return {?} */ () => this._adjustParentForFullscreenChange())); } /** * @private * @return {?} */ _adjustParentForFullscreenChange() { if (!this._containerElement) { return; } /** @type {?} */ const fullscreenElement = this.getFullscreenElement(); /** @type {?} */ const parent = fullscreenElement || this._document.body; parent.appendChild(this._containerElement); } /** * @private * @param {?} fn * @return {?} */ _addFullscreenChangeListener(fn) { /** @type {?} */ const eventName = this._getEventName(); if (eventName) { if (this._fullScreenListener) { this._document.removeEventListener(eventName, this._fullScreenListener); } this._document.addEventListener(eventName, fn); this._fullScreenListener = fn; } } /** * @private * @return {?} */ _getEventName() { if (!this._fullScreenEventName) { /** @type {?} */ const _document = (/** @type {?} */ (this._document)); if (_document.fullscreenEnabled) { this._fullScreenEventName = 'fullscreenchange'; } else if (_document.webkitFullscreenEnabled) { this._fullScreenEventName = 'webkitfullscreenchange'; } else if (_document.mozFullScreenEnabled) { this._fullScreenEventName = 'mozfullscreenchange'; } else if (_document.msFullscreenEnabled) { this._fullScreenEventName = 'MSFullscreenChange'; } } return this._fullScreenEventName; } /** * When the page is put into fullscreen mode, a specific element is specified. * Only that element and its children are visible when in fullscreen mode. * @return {?} */ getFullscreenElement() { /** @type {?} */ const _document = (/** @type {?} */ (this._document)); return _document.fullscreenElement || _document.webkitFullscreenElement || _document.mozFullScreenElement || _document.msFullscreenElement || null; } } FullscreenOverlayContainer.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; /** @nocollapse */ FullscreenOverlayContainer.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: Platform } ]; /** @nocollapse */ FullscreenOverlayContainer.ɵprov = ɵɵdefineInjectable({ factory: function FullscreenOverlayContainer_Factory() { return new FullscreenOverlayContainer(ɵɵinject(DOCUMENT), ɵɵinject(Platform)); }, token: FullscreenOverlayContainer, providedIn: "root" }); if (false) { /** * @type {?} * @private */ FullscreenOverlayContainer.prototype._fullScreenEventName; /** * @type {?} * @private */ FullscreenOverlayContainer.prototype._fullScreenListener; } /** * @fileoverview added by tsickle * Generated from: src/cdk/overlay/public-api.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Generated bundle index. Do not edit. */ export { BlockScrollStrategy, CdkConnectedOverlay, CdkOverlayOrigin, CloseScrollStrategy, ConnectedOverlayPositionChange, ConnectedPositionStrategy, ConnectionPositionPair, FlexibleConnectedPositionStrategy, FullscreenOverlayContainer, GlobalPositionStrategy, NoopScrollStrategy, OVERLAY_PROVIDERS, Overlay, OverlayConfig, OverlayContainer, OverlayKeyboardDispatcher, OverlayModule, OverlayPositionBuilder, OverlayRef, RepositionScrollStrategy, ScrollStrategyOptions, ScrollingVisibility, validateHorizontalPosition, validateVerticalPosition, OVERLAY_CONTAINER_PROVIDER_FACTORY as ɵangular_material_src_cdk_overlay_overlay_a, OVERLAY_CONTAINER_PROVIDER as ɵangular_material_src_cdk_overlay_overlay_b, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY as ɵangular_material_src_cdk_overlay_overlay_c, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY as ɵangular_material_src_cdk_overlay_overlay_d, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER as ɵangular_material_src_cdk_overlay_overlay_e, OVERLAY_KEYBOARD_DISPATCHER_PROVIDER_FACTORY as ɵangular_material_src_cdk_overlay_overlay_f, OVERLAY_KEYBOARD_DISPATCHER_PROVIDER as ɵangular_material_src_cdk_overlay_overlay_g }; //# sourceMappingURL=overlay.js.map