source: trip-planner-front/node_modules/@angular/cdk/__ivy_ngcc__/fesm2015/overlay.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 144.2 KB
Line 
1import * as i1 from '@angular/cdk/scrolling';
2import { ScrollDispatcher, ViewportRuler, ScrollingModule } from '@angular/cdk/scrolling';
3import * as ɵngcc0 from '@angular/core';
4import * as ɵngcc1 from '@angular/cdk/scrolling';
5import * as ɵngcc2 from '@angular/cdk/platform';
6import * as ɵngcc3 from '@angular/cdk/bidi';
7import * as ɵngcc4 from '@angular/common';
8export { CdkScrollable, ScrollDispatcher, ViewportRuler } from '@angular/cdk/scrolling';
9import * as i1$1 from '@angular/common';
10import { DOCUMENT, Location } from '@angular/common';
11import * as i0 from '@angular/core';
12import { Injectable, NgZone, Inject, Optional, ElementRef, ApplicationRef, ComponentFactoryResolver, Injector, InjectionToken, Directive, EventEmitter, TemplateRef, ViewContainerRef, Input, Output, NgModule } from '@angular/core';
13import { coerceCssPixelValue, coerceArray, coerceBooleanProperty } from '@angular/cdk/coercion';
14import * as i2 from '@angular/cdk/platform';
15import { supportsScrollBehavior, _getEventTarget, Platform, _isTestEnvironment } from '@angular/cdk/platform';
16import { Directionality, BidiModule } from '@angular/cdk/bidi';
17import { DomPortalOutlet, TemplatePortal, PortalModule } from '@angular/cdk/portal';
18import { Subject, Subscription, merge } from 'rxjs';
19import { take, takeUntil, takeWhile } from 'rxjs/operators';
20import { ESCAPE, hasModifierKey } from '@angular/cdk/keycodes';
21
22/**
23 * @license
24 * Copyright Google LLC All Rights Reserved.
25 *
26 * Use of this source code is governed by an MIT-style license that can be
27 * found in the LICENSE file at https://angular.io/license
28 */
29const scrollBehaviorSupported = supportsScrollBehavior();
30/**
31 * Strategy that will prevent the user from scrolling while the overlay is visible.
32 */
33class BlockScrollStrategy {
34 constructor(_viewportRuler, document) {
35 this._viewportRuler = _viewportRuler;
36 this._previousHTMLStyles = { top: '', left: '' };
37 this._isEnabled = false;
38 this._document = document;
39 }
40 /** Attaches this scroll strategy to an overlay. */
41 attach() { }
42 /** Blocks page-level scroll while the attached overlay is open. */
43 enable() {
44 if (this._canBeEnabled()) {
45 const root = this._document.documentElement;
46 this._previousScrollPosition = this._viewportRuler.getViewportScrollPosition();
47 // Cache the previous inline styles in case the user had set them.
48 this._previousHTMLStyles.left = root.style.left || '';
49 this._previousHTMLStyles.top = root.style.top || '';
50 // Note: we're using the `html` node, instead of the `body`, because the `body` may
51 // have the user agent margin, whereas the `html` is guaranteed not to have one.
52 root.style.left = coerceCssPixelValue(-this._previousScrollPosition.left);
53 root.style.top = coerceCssPixelValue(-this._previousScrollPosition.top);
54 root.classList.add('cdk-global-scrollblock');
55 this._isEnabled = true;
56 }
57 }
58 /** Unblocks page-level scroll while the attached overlay is open. */
59 disable() {
60 if (this._isEnabled) {
61 const html = this._document.documentElement;
62 const body = this._document.body;
63 const htmlStyle = html.style;
64 const bodyStyle = body.style;
65 const previousHtmlScrollBehavior = htmlStyle.scrollBehavior || '';
66 const previousBodyScrollBehavior = bodyStyle.scrollBehavior || '';
67 this._isEnabled = false;
68 htmlStyle.left = this._previousHTMLStyles.left;
69 htmlStyle.top = this._previousHTMLStyles.top;
70 html.classList.remove('cdk-global-scrollblock');
71 // Disable user-defined smooth scrolling temporarily while we restore the scroll position.
72 // See https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior
73 // Note that we don't mutate the property if the browser doesn't support `scroll-behavior`,
74 // because it can throw off feature detections in `supportsScrollBehavior` which
75 // checks for `'scrollBehavior' in documentElement.style`.
76 if (scrollBehaviorSupported) {
77 htmlStyle.scrollBehavior = bodyStyle.scrollBehavior = 'auto';
78 }
79 window.scroll(this._previousScrollPosition.left, this._previousScrollPosition.top);
80 if (scrollBehaviorSupported) {
81 htmlStyle.scrollBehavior = previousHtmlScrollBehavior;
82 bodyStyle.scrollBehavior = previousBodyScrollBehavior;
83 }
84 }
85 }
86 _canBeEnabled() {
87 // Since the scroll strategies can't be singletons, we have to use a global CSS class
88 // (`cdk-global-scrollblock`) to make sure that we don't try to disable global
89 // scrolling multiple times.
90 const html = this._document.documentElement;
91 if (html.classList.contains('cdk-global-scrollblock') || this._isEnabled) {
92 return false;
93 }
94 const body = this._document.body;
95 const viewport = this._viewportRuler.getViewportSize();
96 return body.scrollHeight > viewport.height || body.scrollWidth > viewport.width;
97 }
98}
99
100/**
101 * @license
102 * Copyright Google LLC All Rights Reserved.
103 *
104 * Use of this source code is governed by an MIT-style license that can be
105 * found in the LICENSE file at https://angular.io/license
106 */
107/**
108 * Returns an error to be thrown when attempting to attach an already-attached scroll strategy.
109 */
110function getMatScrollStrategyAlreadyAttachedError() {
111 return Error(`Scroll strategy has already been attached.`);
112}
113
114/**
115 * Strategy that will close the overlay as soon as the user starts scrolling.
116 */
117class CloseScrollStrategy {
118 constructor(_scrollDispatcher, _ngZone, _viewportRuler, _config) {
119 this._scrollDispatcher = _scrollDispatcher;
120 this._ngZone = _ngZone;
121 this._viewportRuler = _viewportRuler;
122 this._config = _config;
123 this._scrollSubscription = null;
124 /** Detaches the overlay ref and disables the scroll strategy. */
125 this._detach = () => {
126 this.disable();
127 if (this._overlayRef.hasAttached()) {
128 this._ngZone.run(() => this._overlayRef.detach());
129 }
130 };
131 }
132 /** Attaches this scroll strategy to an overlay. */
133 attach(overlayRef) {
134 if (this._overlayRef && (typeof ngDevMode === 'undefined' || ngDevMode)) {
135 throw getMatScrollStrategyAlreadyAttachedError();
136 }
137 this._overlayRef = overlayRef;
138 }
139 /** Enables the closing of the attached overlay on scroll. */
140 enable() {
141 if (this._scrollSubscription) {
142 return;
143 }
144 const stream = this._scrollDispatcher.scrolled(0);
145 if (this._config && this._config.threshold && this._config.threshold > 1) {
146 this._initialScrollPosition = this._viewportRuler.getViewportScrollPosition().top;
147 this._scrollSubscription = stream.subscribe(() => {
148 const scrollPosition = this._viewportRuler.getViewportScrollPosition().top;
149 if (Math.abs(scrollPosition - this._initialScrollPosition) > this._config.threshold) {
150 this._detach();
151 }
152 else {
153 this._overlayRef.updatePosition();
154 }
155 });
156 }
157 else {
158 this._scrollSubscription = stream.subscribe(this._detach);
159 }
160 }
161 /** Disables the closing the attached overlay on scroll. */
162 disable() {
163 if (this._scrollSubscription) {
164 this._scrollSubscription.unsubscribe();
165 this._scrollSubscription = null;
166 }
167 }
168 detach() {
169 this.disable();
170 this._overlayRef = null;
171 }
172}
173
174/**
175 * @license
176 * Copyright Google LLC All Rights Reserved.
177 *
178 * Use of this source code is governed by an MIT-style license that can be
179 * found in the LICENSE file at https://angular.io/license
180 */
181/** Scroll strategy that doesn't do anything. */
182class NoopScrollStrategy {
183 /** Does nothing, as this scroll strategy is a no-op. */
184 enable() { }
185 /** Does nothing, as this scroll strategy is a no-op. */
186 disable() { }
187 /** Does nothing, as this scroll strategy is a no-op. */
188 attach() { }
189}
190
191/**
192 * @license
193 * Copyright Google LLC All Rights Reserved.
194 *
195 * Use of this source code is governed by an MIT-style license that can be
196 * found in the LICENSE file at https://angular.io/license
197 */
198// TODO(jelbourn): move this to live with the rest of the scrolling code
199// TODO(jelbourn): someday replace this with IntersectionObservers
200/**
201 * Gets whether an element is scrolled outside of view by any of its parent scrolling containers.
202 * @param element Dimensions of the element (from getBoundingClientRect)
203 * @param scrollContainers Dimensions of element's scrolling containers (from getBoundingClientRect)
204 * @returns Whether the element is scrolled out of view
205 * @docs-private
206 */
207function isElementScrolledOutsideView(element, scrollContainers) {
208 return scrollContainers.some(containerBounds => {
209 const outsideAbove = element.bottom < containerBounds.top;
210 const outsideBelow = element.top > containerBounds.bottom;
211 const outsideLeft = element.right < containerBounds.left;
212 const outsideRight = element.left > containerBounds.right;
213 return outsideAbove || outsideBelow || outsideLeft || outsideRight;
214 });
215}
216/**
217 * Gets whether an element is clipped by any of its scrolling containers.
218 * @param element Dimensions of the element (from getBoundingClientRect)
219 * @param scrollContainers Dimensions of element's scrolling containers (from getBoundingClientRect)
220 * @returns Whether the element is clipped
221 * @docs-private
222 */
223function isElementClippedByScrolling(element, scrollContainers) {
224 return scrollContainers.some(scrollContainerRect => {
225 const clippedAbove = element.top < scrollContainerRect.top;
226 const clippedBelow = element.bottom > scrollContainerRect.bottom;
227 const clippedLeft = element.left < scrollContainerRect.left;
228 const clippedRight = element.right > scrollContainerRect.right;
229 return clippedAbove || clippedBelow || clippedLeft || clippedRight;
230 });
231}
232
233/**
234 * @license
235 * Copyright Google LLC All Rights Reserved.
236 *
237 * Use of this source code is governed by an MIT-style license that can be
238 * found in the LICENSE file at https://angular.io/license
239 */
240/**
241 * Strategy that will update the element position as the user is scrolling.
242 */
243class RepositionScrollStrategy {
244 constructor(_scrollDispatcher, _viewportRuler, _ngZone, _config) {
245 this._scrollDispatcher = _scrollDispatcher;
246 this._viewportRuler = _viewportRuler;
247 this._ngZone = _ngZone;
248 this._config = _config;
249 this._scrollSubscription = null;
250 }
251 /** Attaches this scroll strategy to an overlay. */
252 attach(overlayRef) {
253 if (this._overlayRef && (typeof ngDevMode === 'undefined' || ngDevMode)) {
254 throw getMatScrollStrategyAlreadyAttachedError();
255 }
256 this._overlayRef = overlayRef;
257 }
258 /** Enables repositioning of the attached overlay on scroll. */
259 enable() {
260 if (!this._scrollSubscription) {
261 const throttle = this._config ? this._config.scrollThrottle : 0;
262 this._scrollSubscription = this._scrollDispatcher.scrolled(throttle).subscribe(() => {
263 this._overlayRef.updatePosition();
264 // TODO(crisbeto): make `close` on by default once all components can handle it.
265 if (this._config && this._config.autoClose) {
266 const overlayRect = this._overlayRef.overlayElement.getBoundingClientRect();
267 const { width, height } = this._viewportRuler.getViewportSize();
268 // TODO(crisbeto): include all ancestor scroll containers here once
269 // we have a way of exposing the trigger element to the scroll strategy.
270 const parentRects = [{ width, height, bottom: height, right: width, top: 0, left: 0 }];
271 if (isElementScrolledOutsideView(overlayRect, parentRects)) {
272 this.disable();
273 this._ngZone.run(() => this._overlayRef.detach());
274 }
275 }
276 });
277 }
278 }
279 /** Disables repositioning of the attached overlay on scroll. */
280 disable() {
281 if (this._scrollSubscription) {
282 this._scrollSubscription.unsubscribe();
283 this._scrollSubscription = null;
284 }
285 }
286 detach() {
287 this.disable();
288 this._overlayRef = null;
289 }
290}
291
292/**
293 * @license
294 * Copyright Google LLC All Rights Reserved.
295 *
296 * Use of this source code is governed by an MIT-style license that can be
297 * found in the LICENSE file at https://angular.io/license
298 */
299/**
300 * Options for how an overlay will handle scrolling.
301 *
302 * Users can provide a custom value for `ScrollStrategyOptions` to replace the default
303 * behaviors. This class primarily acts as a factory for ScrollStrategy instances.
304 */
305class ScrollStrategyOptions {
306 constructor(_scrollDispatcher, _viewportRuler, _ngZone, document) {
307 this._scrollDispatcher = _scrollDispatcher;
308 this._viewportRuler = _viewportRuler;
309 this._ngZone = _ngZone;
310 /** Do nothing on scroll. */
311 this.noop = () => new NoopScrollStrategy();
312 /**
313 * Close the overlay as soon as the user scrolls.
314 * @param config Configuration to be used inside the scroll strategy.
315 */
316 this.close = (config) => new CloseScrollStrategy(this._scrollDispatcher, this._ngZone, this._viewportRuler, config);
317 /** Block scrolling. */
318 this.block = () => new BlockScrollStrategy(this._viewportRuler, this._document);
319 /**
320 * Update the overlay's position on scroll.
321 * @param config Configuration to be used inside the scroll strategy.
322 * Allows debouncing the reposition calls.
323 */
324 this.reposition = (config) => new RepositionScrollStrategy(this._scrollDispatcher, this._viewportRuler, this._ngZone, config);
325 this._document = document;
326 }
327}
328ScrollStrategyOptions.ɵfac = function ScrollStrategyOptions_Factory(t) { return new (t || ScrollStrategyOptions)(ɵngcc0.ɵɵinject(ɵngcc1.ScrollDispatcher), ɵngcc0.ɵɵinject(ɵngcc1.ViewportRuler), ɵngcc0.ɵɵinject(ɵngcc0.NgZone), ɵngcc0.ɵɵinject(DOCUMENT)); };
329ScrollStrategyOptions.ɵprov = i0.ɵɵdefineInjectable({ factory: function ScrollStrategyOptions_Factory() { return new ScrollStrategyOptions(i0.ɵɵinject(i1.ScrollDispatcher), i0.ɵɵinject(i1.ViewportRuler), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i1$1.DOCUMENT)); }, token: ScrollStrategyOptions, providedIn: "root" });
330ScrollStrategyOptions.ctorParameters = () => [
331 { type: ScrollDispatcher },
332 { type: ViewportRuler },
333 { type: NgZone },
334 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
335];
336(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(ScrollStrategyOptions, [{
337 type: Injectable,
338 args: [{ providedIn: 'root' }]
339 }], function () { return [{ type: ɵngcc1.ScrollDispatcher }, { type: ɵngcc1.ViewportRuler }, { type: ɵngcc0.NgZone }, { type: undefined, decorators: [{
340 type: Inject,
341 args: [DOCUMENT]
342 }] }]; }, null); })();
343
344/**
345 * @license
346 * Copyright Google LLC All Rights Reserved.
347 *
348 * Use of this source code is governed by an MIT-style license that can be
349 * found in the LICENSE file at https://angular.io/license
350 */
351
352/**
353 * @license
354 * Copyright Google LLC All Rights Reserved.
355 *
356 * Use of this source code is governed by an MIT-style license that can be
357 * found in the LICENSE file at https://angular.io/license
358 */
359/** Initial configuration used when creating an overlay. */
360class OverlayConfig {
361 constructor(config) {
362 /** Strategy to be used when handling scroll events while the overlay is open. */
363 this.scrollStrategy = new NoopScrollStrategy();
364 /** Custom class to add to the overlay pane. */
365 this.panelClass = '';
366 /** Whether the overlay has a backdrop. */
367 this.hasBackdrop = false;
368 /** Custom class to add to the backdrop */
369 this.backdropClass = 'cdk-overlay-dark-backdrop';
370 /**
371 * Whether the overlay should be disposed of when the user goes backwards/forwards in history.
372 * Note that this usually doesn't include clicking on links (unless the user is using
373 * the `HashLocationStrategy`).
374 */
375 this.disposeOnNavigation = false;
376 if (config) {
377 // Use `Iterable` instead of `Array` because TypeScript, as of 3.6.3,
378 // loses the array generic type in the `for of`. But we *also* have to use `Array` because
379 // typescript won't iterate over an `Iterable` unless you compile with `--downlevelIteration`
380 const configKeys = Object.keys(config);
381 for (const key of configKeys) {
382 if (config[key] !== undefined) {
383 // TypeScript, as of version 3.5, sees the left-hand-side of this expression
384 // as "I don't know *which* key this is, so the only valid value is the intersection
385 // of all the posible values." In this case, that happens to be `undefined`. TypeScript
386 // is not smart enough to see that the right-hand-side is actually an access of the same
387 // exact type with the same exact key, meaning that the value type must be identical.
388 // So we use `any` to work around this.
389 this[key] = config[key];
390 }
391 }
392 }
393 }
394}
395
396/**
397 * @license
398 * Copyright Google LLC All Rights Reserved.
399 *
400 * Use of this source code is governed by an MIT-style license that can be
401 * found in the LICENSE file at https://angular.io/license
402 */
403/** The points of the origin element and the overlay element to connect. */
404class ConnectionPositionPair {
405 constructor(origin, overlay,
406 /** Offset along the X axis. */
407 offsetX,
408 /** Offset along the Y axis. */
409 offsetY,
410 /** Class(es) to be applied to the panel while this position is active. */
411 panelClass) {
412 this.offsetX = offsetX;
413 this.offsetY = offsetY;
414 this.panelClass = panelClass;
415 this.originX = origin.originX;
416 this.originY = origin.originY;
417 this.overlayX = overlay.overlayX;
418 this.overlayY = overlay.overlayY;
419 }
420}
421/**
422 * Set of properties regarding the position of the origin and overlay relative to the viewport
423 * with respect to the containing Scrollable elements.
424 *
425 * The overlay and origin are clipped if any part of their bounding client rectangle exceeds the
426 * bounds of any one of the strategy's Scrollable's bounding client rectangle.
427 *
428 * The overlay and origin are outside view if there is no overlap between their bounding client
429 * rectangle and any one of the strategy's Scrollable's bounding client rectangle.
430 *
431 * ----------- -----------
432 * | outside | | clipped |
433 * | view | --------------------------
434 * | | | | | |
435 * ---------- | ----------- |
436 * -------------------------- | |
437 * | | | Scrollable |
438 * | | | |
439 * | | --------------------------
440 * | Scrollable |
441 * | |
442 * --------------------------
443 *
444 * @docs-private
445 */
446class ScrollingVisibility {
447}
448/** The change event emitted by the strategy when a fallback position is used. */
449class ConnectedOverlayPositionChange {
450 constructor(
451 /** The position used as a result of this change. */
452 connectionPair,
453 /** @docs-private */
454 scrollableViewProperties) {
455 this.connectionPair = connectionPair;
456 this.scrollableViewProperties = scrollableViewProperties;
457 }
458}
459ConnectedOverlayPositionChange.ctorParameters = () => [
460 { type: ConnectionPositionPair },
461 { type: ScrollingVisibility, decorators: [{ type: Optional }] }
462];
463/**
464 * Validates whether a vertical position property matches the expected values.
465 * @param property Name of the property being validated.
466 * @param value Value of the property being validated.
467 * @docs-private
468 */
469function validateVerticalPosition(property, value) {
470 if (value !== 'top' && value !== 'bottom' && value !== 'center') {
471 throw Error(`ConnectedPosition: Invalid ${property} "${value}". ` +
472 `Expected "top", "bottom" or "center".`);
473 }
474}
475/**
476 * Validates whether a horizontal position property matches the expected values.
477 * @param property Name of the property being validated.
478 * @param value Value of the property being validated.
479 * @docs-private
480 */
481function validateHorizontalPosition(property, value) {
482 if (value !== 'start' && value !== 'end' && value !== 'center') {
483 throw Error(`ConnectedPosition: Invalid ${property} "${value}". ` +
484 `Expected "start", "end" or "center".`);
485 }
486}
487
488/**
489 * @license
490 * Copyright Google LLC All Rights Reserved.
491 *
492 * Use of this source code is governed by an MIT-style license that can be
493 * found in the LICENSE file at https://angular.io/license
494 */
495/**
496 * Service for dispatching events that land on the body to appropriate overlay ref,
497 * if any. It maintains a list of attached overlays to determine best suited overlay based
498 * on event target and order of overlay opens.
499 */
500class BaseOverlayDispatcher {
501 constructor(document) {
502 /** Currently attached overlays in the order they were attached. */
503 this._attachedOverlays = [];
504 this._document = document;
505 }
506 ngOnDestroy() {
507 this.detach();
508 }
509 /** Add a new overlay to the list of attached overlay refs. */
510 add(overlayRef) {
511 // Ensure that we don't get the same overlay multiple times.
512 this.remove(overlayRef);
513 this._attachedOverlays.push(overlayRef);
514 }
515 /** Remove an overlay from the list of attached overlay refs. */
516 remove(overlayRef) {
517 const index = this._attachedOverlays.indexOf(overlayRef);
518 if (index > -1) {
519 this._attachedOverlays.splice(index, 1);
520 }
521 // Remove the global listener once there are no more overlays.
522 if (this._attachedOverlays.length === 0) {
523 this.detach();
524 }
525 }
526}
527BaseOverlayDispatcher.ɵfac = function BaseOverlayDispatcher_Factory(t) { return new (t || BaseOverlayDispatcher)(ɵngcc0.ɵɵinject(DOCUMENT)); };
528BaseOverlayDispatcher.ɵprov = i0.ɵɵdefineInjectable({ factory: function BaseOverlayDispatcher_Factory() { return new BaseOverlayDispatcher(i0.ɵɵinject(i1$1.DOCUMENT)); }, token: BaseOverlayDispatcher, providedIn: "root" });
529BaseOverlayDispatcher.ctorParameters = () => [
530 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
531];
532(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(BaseOverlayDispatcher, [{
533 type: Injectable,
534 args: [{ providedIn: 'root' }]
535 }], function () { return [{ type: undefined, decorators: [{
536 type: Inject,
537 args: [DOCUMENT]
538 }] }]; }, null); })();
539
540/**
541 * @license
542 * Copyright Google LLC All Rights Reserved.
543 *
544 * Use of this source code is governed by an MIT-style license that can be
545 * found in the LICENSE file at https://angular.io/license
546 */
547/**
548 * Service for dispatching keyboard events that land on the body to appropriate overlay ref,
549 * if any. It maintains a list of attached overlays to determine best suited overlay based
550 * on event target and order of overlay opens.
551 */
552class OverlayKeyboardDispatcher extends BaseOverlayDispatcher {
553 constructor(document) {
554 super(document);
555 /** Keyboard event listener that will be attached to the body. */
556 this._keydownListener = (event) => {
557 const overlays = this._attachedOverlays;
558 for (let i = overlays.length - 1; i > -1; i--) {
559 // Dispatch the keydown event to the top overlay which has subscribers to its keydown events.
560 // We want to target the most recent overlay, rather than trying to match where the event came
561 // from, because some components might open an overlay, but keep focus on a trigger element
562 // (e.g. for select and autocomplete). We skip overlays without keydown event subscriptions,
563 // because we don't want overlays that don't handle keyboard events to block the ones below
564 // them that do.
565 if (overlays[i]._keydownEvents.observers.length > 0) {
566 overlays[i]._keydownEvents.next(event);
567 break;
568 }
569 }
570 };
571 }
572 /** Add a new overlay to the list of attached overlay refs. */
573 add(overlayRef) {
574 super.add(overlayRef);
575 // Lazily start dispatcher once first overlay is added
576 if (!this._isAttached) {
577 this._document.body.addEventListener('keydown', this._keydownListener);
578 this._isAttached = true;
579 }
580 }
581 /** Detaches the global keyboard event listener. */
582 detach() {
583 if (this._isAttached) {
584 this._document.body.removeEventListener('keydown', this._keydownListener);
585 this._isAttached = false;
586 }
587 }
588}
589OverlayKeyboardDispatcher.ɵfac = function OverlayKeyboardDispatcher_Factory(t) { return new (t || OverlayKeyboardDispatcher)(ɵngcc0.ɵɵinject(DOCUMENT)); };
590OverlayKeyboardDispatcher.ɵprov = i0.ɵɵdefineInjectable({ factory: function OverlayKeyboardDispatcher_Factory() { return new OverlayKeyboardDispatcher(i0.ɵɵinject(i1$1.DOCUMENT)); }, token: OverlayKeyboardDispatcher, providedIn: "root" });
591OverlayKeyboardDispatcher.ctorParameters = () => [
592 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
593];
594(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(OverlayKeyboardDispatcher, [{
595 type: Injectable,
596 args: [{ providedIn: 'root' }]
597 }], function () { return [{ type: undefined, decorators: [{
598 type: Inject,
599 args: [DOCUMENT]
600 }] }]; }, null); })();
601
602/**
603 * @license
604 * Copyright Google LLC All Rights Reserved.
605 *
606 * Use of this source code is governed by an MIT-style license that can be
607 * found in the LICENSE file at https://angular.io/license
608 */
609/**
610 * Service for dispatching mouse click events that land on the body to appropriate overlay ref,
611 * if any. It maintains a list of attached overlays to determine best suited overlay based
612 * on event target and order of overlay opens.
613 */
614class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
615 constructor(document, _platform) {
616 super(document);
617 this._platform = _platform;
618 this._cursorStyleIsSet = false;
619 /** Click event listener that will be attached to the body propagate phase. */
620 this._clickListener = (event) => {
621 const target = _getEventTarget(event);
622 // We copy the array because the original may be modified asynchronously if the
623 // outsidePointerEvents listener decides to detach overlays resulting in index errors inside
624 // the for loop.
625 const overlays = this._attachedOverlays.slice();
626 // Dispatch the mouse event to the top overlay which has subscribers to its mouse events.
627 // We want to target all overlays for which the click could be considered as outside click.
628 // As soon as we reach an overlay for which the click is not outside click we break off
629 // the loop.
630 for (let i = overlays.length - 1; i > -1; i--) {
631 const overlayRef = overlays[i];
632 if (overlayRef._outsidePointerEvents.observers.length < 1 || !overlayRef.hasAttached()) {
633 continue;
634 }
635 // If it's a click inside the overlay, just break - we should do nothing
636 // If it's an outside click dispatch the mouse event, and proceed with the next overlay
637 if (overlayRef.overlayElement.contains(target)) {
638 break;
639 }
640 overlayRef._outsidePointerEvents.next(event);
641 }
642 };
643 }
644 /** Add a new overlay to the list of attached overlay refs. */
645 add(overlayRef) {
646 super.add(overlayRef);
647 // Safari on iOS does not generate click events for non-interactive
648 // elements. However, we want to receive a click for any element outside
649 // the overlay. We can force a "clickable" state by setting
650 // `cursor: pointer` on the document body. See:
651 // https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#Safari_Mobile
652 // https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
653 if (!this._isAttached) {
654 const body = this._document.body;
655 body.addEventListener('click', this._clickListener, true);
656 body.addEventListener('auxclick', this._clickListener, true);
657 body.addEventListener('contextmenu', this._clickListener, true);
658 // click event is not fired on iOS. To make element "clickable" we are
659 // setting the cursor to pointer
660 if (this._platform.IOS && !this._cursorStyleIsSet) {
661 this._cursorOriginalValue = body.style.cursor;
662 body.style.cursor = 'pointer';
663 this._cursorStyleIsSet = true;
664 }
665 this._isAttached = true;
666 }
667 }
668 /** Detaches the global keyboard event listener. */
669 detach() {
670 if (this._isAttached) {
671 const body = this._document.body;
672 body.removeEventListener('click', this._clickListener, true);
673 body.removeEventListener('auxclick', this._clickListener, true);
674 body.removeEventListener('contextmenu', this._clickListener, true);
675 if (this._platform.IOS && this._cursorStyleIsSet) {
676 body.style.cursor = this._cursorOriginalValue;
677 this._cursorStyleIsSet = false;
678 }
679 this._isAttached = false;
680 }
681 }
682}
683OverlayOutsideClickDispatcher.ɵfac = function OverlayOutsideClickDispatcher_Factory(t) { return new (t || OverlayOutsideClickDispatcher)(ɵngcc0.ɵɵinject(DOCUMENT), ɵngcc0.ɵɵinject(ɵngcc2.Platform)); };
684OverlayOutsideClickDispatcher.ɵprov = i0.ɵɵdefineInjectable({ factory: function OverlayOutsideClickDispatcher_Factory() { return new OverlayOutsideClickDispatcher(i0.ɵɵinject(i1$1.DOCUMENT), i0.ɵɵinject(i2.Platform)); }, token: OverlayOutsideClickDispatcher, providedIn: "root" });
685OverlayOutsideClickDispatcher.ctorParameters = () => [
686 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
687 { type: Platform }
688];
689(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(OverlayOutsideClickDispatcher, [{
690 type: Injectable,
691 args: [{ providedIn: 'root' }]
692 }], function () { return [{ type: undefined, decorators: [{
693 type: Inject,
694 args: [DOCUMENT]
695 }] }, { type: ɵngcc2.Platform }]; }, null); })();
696
697/**
698 * @license
699 * Copyright Google LLC All Rights Reserved.
700 *
701 * Use of this source code is governed by an MIT-style license that can be
702 * found in the LICENSE file at https://angular.io/license
703 */
704/** Container inside which all overlays will render. */
705class OverlayContainer {
706 constructor(document, _platform) {
707 this._platform = _platform;
708 this._document = document;
709 }
710 ngOnDestroy() {
711 const container = this._containerElement;
712 if (container && container.parentNode) {
713 container.parentNode.removeChild(container);
714 }
715 }
716 /**
717 * This method returns the overlay container element. It will lazily
718 * create the element the first time it is called to facilitate using
719 * the container in non-browser environments.
720 * @returns the container element
721 */
722 getContainerElement() {
723 if (!this._containerElement) {
724 this._createContainer();
725 }
726 return this._containerElement;
727 }
728 /**
729 * Create the overlay container element, which is simply a div
730 * with the 'cdk-overlay-container' class on the document body.
731 */
732 _createContainer() {
733 const containerClass = 'cdk-overlay-container';
734 // TODO(crisbeto): remove the testing check once we have an overlay testing
735 // module or Angular starts tearing down the testing `NgModule`. See:
736 // https://github.com/angular/angular/issues/18831
737 if (this._platform.isBrowser || _isTestEnvironment()) {
738 const oppositePlatformContainers = this._document.querySelectorAll(`.${containerClass}[platform="server"], ` +
739 `.${containerClass}[platform="test"]`);
740 // Remove any old containers from the opposite platform.
741 // This can happen when transitioning from the server to the client.
742 for (let i = 0; i < oppositePlatformContainers.length; i++) {
743 oppositePlatformContainers[i].parentNode.removeChild(oppositePlatformContainers[i]);
744 }
745 }
746 const container = this._document.createElement('div');
747 container.classList.add(containerClass);
748 // A long time ago we kept adding new overlay containers whenever a new app was instantiated,
749 // but at some point we added logic which clears the duplicate ones in order to avoid leaks.
750 // The new logic was a little too aggressive since it was breaking some legitimate use cases.
751 // To mitigate the problem we made it so that only containers from a different platform are
752 // cleared, but the side-effect was that people started depending on the overly-aggressive
753 // logic to clean up their tests for them. Until we can introduce an overlay-specific testing
754 // module which does the cleanup, we try to detect that we're in a test environment and we
755 // always clear the container. See #17006.
756 // TODO(crisbeto): remove the test environment check once we have an overlay testing module.
757 if (_isTestEnvironment()) {
758 container.setAttribute('platform', 'test');
759 }
760 else if (!this._platform.isBrowser) {
761 container.setAttribute('platform', 'server');
762 }
763 this._document.body.appendChild(container);
764 this._containerElement = container;
765 }
766}
767OverlayContainer.ɵfac = function OverlayContainer_Factory(t) { return new (t || OverlayContainer)(ɵngcc0.ɵɵinject(DOCUMENT), ɵngcc0.ɵɵinject(ɵngcc2.Platform)); };
768OverlayContainer.ɵprov = i0.ɵɵdefineInjectable({ factory: function OverlayContainer_Factory() { return new OverlayContainer(i0.ɵɵinject(i1$1.DOCUMENT), i0.ɵɵinject(i2.Platform)); }, token: OverlayContainer, providedIn: "root" });
769OverlayContainer.ctorParameters = () => [
770 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
771 { type: Platform }
772];
773(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(OverlayContainer, [{
774 type: Injectable,
775 args: [{ providedIn: 'root' }]
776 }], function () { return [{ type: undefined, decorators: [{
777 type: Inject,
778 args: [DOCUMENT]
779 }] }, { type: ɵngcc2.Platform }]; }, null); })();
780
781/**
782 * @license
783 * Copyright Google LLC All Rights Reserved.
784 *
785 * Use of this source code is governed by an MIT-style license that can be
786 * found in the LICENSE file at https://angular.io/license
787 */
788/**
789 * Reference to an overlay that has been created with the Overlay service.
790 * Used to manipulate or dispose of said overlay.
791 */
792class OverlayRef {
793 constructor(_portalOutlet, _host, _pane, _config, _ngZone, _keyboardDispatcher, _document, _location, _outsideClickDispatcher) {
794 this._portalOutlet = _portalOutlet;
795 this._host = _host;
796 this._pane = _pane;
797 this._config = _config;
798 this._ngZone = _ngZone;
799 this._keyboardDispatcher = _keyboardDispatcher;
800 this._document = _document;
801 this._location = _location;
802 this._outsideClickDispatcher = _outsideClickDispatcher;
803 this._backdropElement = null;
804 this._backdropClick = new Subject();
805 this._attachments = new Subject();
806 this._detachments = new Subject();
807 this._locationChanges = Subscription.EMPTY;
808 this._backdropClickHandler = (event) => this._backdropClick.next(event);
809 /** Stream of keydown events dispatched to this overlay. */
810 this._keydownEvents = new Subject();
811 /** Stream of mouse outside events dispatched to this overlay. */
812 this._outsidePointerEvents = new Subject();
813 if (_config.scrollStrategy) {
814 this._scrollStrategy = _config.scrollStrategy;
815 this._scrollStrategy.attach(this);
816 }
817 this._positionStrategy = _config.positionStrategy;
818 }
819 /** The overlay's HTML element */
820 get overlayElement() {
821 return this._pane;
822 }
823 /** The overlay's backdrop HTML element. */
824 get backdropElement() {
825 return this._backdropElement;
826 }
827 /**
828 * Wrapper around the panel element. Can be used for advanced
829 * positioning where a wrapper with specific styling is
830 * required around the overlay pane.
831 */
832 get hostElement() {
833 return this._host;
834 }
835 /**
836 * Attaches content, given via a Portal, to the overlay.
837 * If the overlay is configured to have a backdrop, it will be created.
838 *
839 * @param portal Portal instance to which to attach the overlay.
840 * @returns The portal attachment result.
841 */
842 attach(portal) {
843 let attachResult = this._portalOutlet.attach(portal);
844 // Update the pane element with the given configuration.
845 if (!this._host.parentElement && this._previousHostParent) {
846 this._previousHostParent.appendChild(this._host);
847 }
848 if (this._positionStrategy) {
849 this._positionStrategy.attach(this);
850 }
851 this._updateStackingOrder();
852 this._updateElementSize();
853 this._updateElementDirection();
854 if (this._scrollStrategy) {
855 this._scrollStrategy.enable();
856 }
857 // Update the position once the zone is stable so that the overlay will be fully rendered
858 // before attempting to position it, as the position may depend on the size of the rendered
859 // content.
860 this._ngZone.onStable
861 .pipe(take(1))
862 .subscribe(() => {
863 // The overlay could've been detached before the zone has stabilized.
864 if (this.hasAttached()) {
865 this.updatePosition();
866 }
867 });
868 // Enable pointer events for the overlay pane element.
869 this._togglePointerEvents(true);
870 if (this._config.hasBackdrop) {
871 this._attachBackdrop();
872 }
873 if (this._config.panelClass) {
874 this._toggleClasses(this._pane, this._config.panelClass, true);
875 }
876 // Only emit the `attachments` event once all other setup is done.
877 this._attachments.next();
878 // Track this overlay by the keyboard dispatcher
879 this._keyboardDispatcher.add(this);
880 if (this._config.disposeOnNavigation) {
881 this._locationChanges = this._location.subscribe(() => this.dispose());
882 }
883 this._outsideClickDispatcher.add(this);
884 return attachResult;
885 }
886 /**
887 * Detaches an overlay from a portal.
888 * @returns The portal detachment result.
889 */
890 detach() {
891 if (!this.hasAttached()) {
892 return;
893 }
894 this.detachBackdrop();
895 // When the overlay is detached, the pane element should disable pointer events.
896 // This is necessary because otherwise the pane element will cover the page and disable
897 // pointer events therefore. Depends on the position strategy and the applied pane boundaries.
898 this._togglePointerEvents(false);
899 if (this._positionStrategy && this._positionStrategy.detach) {
900 this._positionStrategy.detach();
901 }
902 if (this._scrollStrategy) {
903 this._scrollStrategy.disable();
904 }
905 const detachmentResult = this._portalOutlet.detach();
906 // Only emit after everything is detached.
907 this._detachments.next();
908 // Remove this overlay from keyboard dispatcher tracking.
909 this._keyboardDispatcher.remove(this);
910 // Keeping the host element in the DOM can cause scroll jank, because it still gets
911 // rendered, even though it's transparent and unclickable which is why we remove it.
912 this._detachContentWhenStable();
913 this._locationChanges.unsubscribe();
914 this._outsideClickDispatcher.remove(this);
915 return detachmentResult;
916 }
917 /** Cleans up the overlay from the DOM. */
918 dispose() {
919 const isAttached = this.hasAttached();
920 if (this._positionStrategy) {
921 this._positionStrategy.dispose();
922 }
923 this._disposeScrollStrategy();
924 this._disposeBackdrop(this._backdropElement);
925 this._locationChanges.unsubscribe();
926 this._keyboardDispatcher.remove(this);
927 this._portalOutlet.dispose();
928 this._attachments.complete();
929 this._backdropClick.complete();
930 this._keydownEvents.complete();
931 this._outsidePointerEvents.complete();
932 this._outsideClickDispatcher.remove(this);
933 if (this._host && this._host.parentNode) {
934 this._host.parentNode.removeChild(this._host);
935 this._host = null;
936 }
937 this._previousHostParent = this._pane = null;
938 if (isAttached) {
939 this._detachments.next();
940 }
941 this._detachments.complete();
942 }
943 /** Whether the overlay has attached content. */
944 hasAttached() {
945 return this._portalOutlet.hasAttached();
946 }
947 /** Gets an observable that emits when the backdrop has been clicked. */
948 backdropClick() {
949 return this._backdropClick;
950 }
951 /** Gets an observable that emits when the overlay has been attached. */
952 attachments() {
953 return this._attachments;
954 }
955 /** Gets an observable that emits when the overlay has been detached. */
956 detachments() {
957 return this._detachments;
958 }
959 /** Gets an observable of keydown events targeted to this overlay. */
960 keydownEvents() {
961 return this._keydownEvents;
962 }
963 /** Gets an observable of pointer events targeted outside this overlay. */
964 outsidePointerEvents() {
965 return this._outsidePointerEvents;
966 }
967 /** Gets the current overlay configuration, which is immutable. */
968 getConfig() {
969 return this._config;
970 }
971 /** Updates the position of the overlay based on the position strategy. */
972 updatePosition() {
973 if (this._positionStrategy) {
974 this._positionStrategy.apply();
975 }
976 }
977 /** Switches to a new position strategy and updates the overlay position. */
978 updatePositionStrategy(strategy) {
979 if (strategy === this._positionStrategy) {
980 return;
981 }
982 if (this._positionStrategy) {
983 this._positionStrategy.dispose();
984 }
985 this._positionStrategy = strategy;
986 if (this.hasAttached()) {
987 strategy.attach(this);
988 this.updatePosition();
989 }
990 }
991 /** Update the size properties of the overlay. */
992 updateSize(sizeConfig) {
993 this._config = Object.assign(Object.assign({}, this._config), sizeConfig);
994 this._updateElementSize();
995 }
996 /** Sets the LTR/RTL direction for the overlay. */
997 setDirection(dir) {
998 this._config = Object.assign(Object.assign({}, this._config), { direction: dir });
999 this._updateElementDirection();
1000 }
1001 /** Add a CSS class or an array of classes to the overlay pane. */
1002 addPanelClass(classes) {
1003 if (this._pane) {
1004 this._toggleClasses(this._pane, classes, true);
1005 }
1006 }
1007 /** Remove a CSS class or an array of classes from the overlay pane. */
1008 removePanelClass(classes) {
1009 if (this._pane) {
1010 this._toggleClasses(this._pane, classes, false);
1011 }
1012 }
1013 /**
1014 * Returns the layout direction of the overlay panel.
1015 */
1016 getDirection() {
1017 const direction = this._config.direction;
1018 if (!direction) {
1019 return 'ltr';
1020 }
1021 return typeof direction === 'string' ? direction : direction.value;
1022 }
1023 /** Switches to a new scroll strategy. */
1024 updateScrollStrategy(strategy) {
1025 if (strategy === this._scrollStrategy) {
1026 return;
1027 }
1028 this._disposeScrollStrategy();
1029 this._scrollStrategy = strategy;
1030 if (this.hasAttached()) {
1031 strategy.attach(this);
1032 strategy.enable();
1033 }
1034 }
1035 /** Updates the text direction of the overlay panel. */
1036 _updateElementDirection() {
1037 this._host.setAttribute('dir', this.getDirection());
1038 }
1039 /** Updates the size of the overlay element based on the overlay config. */
1040 _updateElementSize() {
1041 if (!this._pane) {
1042 return;
1043 }
1044 const style = this._pane.style;
1045 style.width = coerceCssPixelValue(this._config.width);
1046 style.height = coerceCssPixelValue(this._config.height);
1047 style.minWidth = coerceCssPixelValue(this._config.minWidth);
1048 style.minHeight = coerceCssPixelValue(this._config.minHeight);
1049 style.maxWidth = coerceCssPixelValue(this._config.maxWidth);
1050 style.maxHeight = coerceCssPixelValue(this._config.maxHeight);
1051 }
1052 /** Toggles the pointer events for the overlay pane element. */
1053 _togglePointerEvents(enablePointer) {
1054 this._pane.style.pointerEvents = enablePointer ? '' : 'none';
1055 }
1056 /** Attaches a backdrop for this overlay. */
1057 _attachBackdrop() {
1058 const showingClass = 'cdk-overlay-backdrop-showing';
1059 this._backdropElement = this._document.createElement('div');
1060 this._backdropElement.classList.add('cdk-overlay-backdrop');
1061 if (this._config.backdropClass) {
1062 this._toggleClasses(this._backdropElement, this._config.backdropClass, true);
1063 }
1064 // Insert the backdrop before the pane in the DOM order,
1065 // in order to handle stacked overlays properly.
1066 this._host.parentElement.insertBefore(this._backdropElement, this._host);
1067 // Forward backdrop clicks such that the consumer of the overlay can perform whatever
1068 // action desired when such a click occurs (usually closing the overlay).
1069 this._backdropElement.addEventListener('click', this._backdropClickHandler);
1070 // Add class to fade-in the backdrop after one frame.
1071 if (typeof requestAnimationFrame !== 'undefined') {
1072 this._ngZone.runOutsideAngular(() => {
1073 requestAnimationFrame(() => {
1074 if (this._backdropElement) {
1075 this._backdropElement.classList.add(showingClass);
1076 }
1077 });
1078 });
1079 }
1080 else {
1081 this._backdropElement.classList.add(showingClass);
1082 }
1083 }
1084 /**
1085 * Updates the stacking order of the element, moving it to the top if necessary.
1086 * This is required in cases where one overlay was detached, while another one,
1087 * that should be behind it, was destroyed. The next time both of them are opened,
1088 * the stacking will be wrong, because the detached element's pane will still be
1089 * in its original DOM position.
1090 */
1091 _updateStackingOrder() {
1092 if (this._host.nextSibling) {
1093 this._host.parentNode.appendChild(this._host);
1094 }
1095 }
1096 /** Detaches the backdrop (if any) associated with the overlay. */
1097 detachBackdrop() {
1098 const backdropToDetach = this._backdropElement;
1099 if (!backdropToDetach) {
1100 return;
1101 }
1102 let timeoutId;
1103 const finishDetach = () => {
1104 // It may not be attached to anything in certain cases (e.g. unit tests).
1105 if (backdropToDetach) {
1106 backdropToDetach.removeEventListener('click', this._backdropClickHandler);
1107 backdropToDetach.removeEventListener('transitionend', finishDetach);
1108 this._disposeBackdrop(backdropToDetach);
1109 }
1110 if (this._config.backdropClass) {
1111 this._toggleClasses(backdropToDetach, this._config.backdropClass, false);
1112 }
1113 clearTimeout(timeoutId);
1114 };
1115 backdropToDetach.classList.remove('cdk-overlay-backdrop-showing');
1116 this._ngZone.runOutsideAngular(() => {
1117 backdropToDetach.addEventListener('transitionend', finishDetach);
1118 });
1119 // If the backdrop doesn't have a transition, the `transitionend` event won't fire.
1120 // In this case we make it unclickable and we try to remove it after a delay.
1121 backdropToDetach.style.pointerEvents = 'none';
1122 // Run this outside the Angular zone because there's nothing that Angular cares about.
1123 // If it were to run inside the Angular zone, every test that used Overlay would have to be
1124 // either async or fakeAsync.
1125 timeoutId = this._ngZone.runOutsideAngular(() => setTimeout(finishDetach, 500));
1126 }
1127 /** Toggles a single CSS class or an array of classes on an element. */
1128 _toggleClasses(element, cssClasses, isAdd) {
1129 const classList = element.classList;
1130 coerceArray(cssClasses).forEach(cssClass => {
1131 // We can't do a spread here, because IE doesn't support setting multiple classes.
1132 // Also trying to add an empty string to a DOMTokenList will throw.
1133 if (cssClass) {
1134 isAdd ? classList.add(cssClass) : classList.remove(cssClass);
1135 }
1136 });
1137 }
1138 /** Detaches the overlay content next time the zone stabilizes. */
1139 _detachContentWhenStable() {
1140 // Normally we wouldn't have to explicitly run this outside the `NgZone`, however
1141 // if the consumer is using `zone-patch-rxjs`, the `Subscription.unsubscribe` call will
1142 // be patched to run inside the zone, which will throw us into an infinite loop.
1143 this._ngZone.runOutsideAngular(() => {
1144 // We can't remove the host here immediately, because the overlay pane's content
1145 // might still be animating. This stream helps us avoid interrupting the animation
1146 // by waiting for the pane to become empty.
1147 const subscription = this._ngZone.onStable
1148 .pipe(takeUntil(merge(this._attachments, this._detachments)))
1149 .subscribe(() => {
1150 // Needs a couple of checks for the pane and host, because
1151 // they may have been removed by the time the zone stabilizes.
1152 if (!this._pane || !this._host || this._pane.children.length === 0) {
1153 if (this._pane && this._config.panelClass) {
1154 this._toggleClasses(this._pane, this._config.panelClass, false);
1155 }
1156 if (this._host && this._host.parentElement) {
1157 this._previousHostParent = this._host.parentElement;
1158 this._previousHostParent.removeChild(this._host);
1159 }
1160 subscription.unsubscribe();
1161 }
1162 });
1163 });
1164 }
1165 /** Disposes of a scroll strategy. */
1166 _disposeScrollStrategy() {
1167 const scrollStrategy = this._scrollStrategy;
1168 if (scrollStrategy) {
1169 scrollStrategy.disable();
1170 if (scrollStrategy.detach) {
1171 scrollStrategy.detach();
1172 }
1173 }
1174 }
1175 /** Removes a backdrop element from the DOM. */
1176 _disposeBackdrop(backdrop) {
1177 if (backdrop) {
1178 if (backdrop.parentNode) {
1179 backdrop.parentNode.removeChild(backdrop);
1180 }
1181 // It is possible that a new portal has been attached to this overlay since we started
1182 // removing the backdrop. If that is the case, only clear the backdrop reference if it
1183 // is still the same instance that we started to remove.
1184 if (this._backdropElement === backdrop) {
1185 this._backdropElement = null;
1186 }
1187 }
1188 }
1189}
1190
1191/**
1192 * @license
1193 * Copyright Google LLC All Rights Reserved.
1194 *
1195 * Use of this source code is governed by an MIT-style license that can be
1196 * found in the LICENSE file at https://angular.io/license
1197 */
1198// TODO: refactor clipping detection into a separate thing (part of scrolling module)
1199// TODO: doesn't handle both flexible width and height when it has to scroll along both axis.
1200/** Class to be added to the overlay bounding box. */
1201const boundingBoxClass = 'cdk-overlay-connected-position-bounding-box';
1202/** Regex used to split a string on its CSS units. */
1203const cssUnitPattern = /([A-Za-z%]+)$/;
1204/**
1205 * A strategy for positioning overlays. Using this strategy, an overlay is given an
1206 * implicit position relative some origin element. The relative position is defined in terms of
1207 * a point on the origin element that is connected to a point on the overlay element. For example,
1208 * a basic dropdown is connecting the bottom-left corner of the origin to the top-left corner
1209 * of the overlay.
1210 */
1211class FlexibleConnectedPositionStrategy {
1212 constructor(connectedTo, _viewportRuler, _document, _platform, _overlayContainer) {
1213 this._viewportRuler = _viewportRuler;
1214 this._document = _document;
1215 this._platform = _platform;
1216 this._overlayContainer = _overlayContainer;
1217 /** Last size used for the bounding box. Used to avoid resizing the overlay after open. */
1218 this._lastBoundingBoxSize = { width: 0, height: 0 };
1219 /** Whether the overlay was pushed in a previous positioning. */
1220 this._isPushed = false;
1221 /** Whether the overlay can be pushed on-screen on the initial open. */
1222 this._canPush = true;
1223 /** Whether the overlay can grow via flexible width/height after the initial open. */
1224 this._growAfterOpen = false;
1225 /** Whether the overlay's width and height can be constrained to fit within the viewport. */
1226 this._hasFlexibleDimensions = true;
1227 /** Whether the overlay position is locked. */
1228 this._positionLocked = false;
1229 /** Amount of space that must be maintained between the overlay and the edge of the viewport. */
1230 this._viewportMargin = 0;
1231 /** The Scrollable containers used to check scrollable view properties on position change. */
1232 this._scrollables = [];
1233 /** Ordered list of preferred positions, from most to least desirable. */
1234 this._preferredPositions = [];
1235 /** Subject that emits whenever the position changes. */
1236 this._positionChanges = new Subject();
1237 /** Subscription to viewport size changes. */
1238 this._resizeSubscription = Subscription.EMPTY;
1239 /** Default offset for the overlay along the x axis. */
1240 this._offsetX = 0;
1241 /** Default offset for the overlay along the y axis. */
1242 this._offsetY = 0;
1243 /** Keeps track of the CSS classes that the position strategy has applied on the overlay panel. */
1244 this._appliedPanelClasses = [];
1245 /** Observable sequence of position changes. */
1246 this.positionChanges = this._positionChanges;
1247 this.setOrigin(connectedTo);
1248 }
1249 /** Ordered list of preferred positions, from most to least desirable. */
1250 get positions() {
1251 return this._preferredPositions;
1252 }
1253 /** Attaches this position strategy to an overlay. */
1254 attach(overlayRef) {
1255 if (this._overlayRef && overlayRef !== this._overlayRef &&
1256 (typeof ngDevMode === 'undefined' || ngDevMode)) {
1257 throw Error('This position strategy is already attached to an overlay');
1258 }
1259 this._validatePositions();
1260 overlayRef.hostElement.classList.add(boundingBoxClass);
1261 this._overlayRef = overlayRef;
1262 this._boundingBox = overlayRef.hostElement;
1263 this._pane = overlayRef.overlayElement;
1264 this._isDisposed = false;
1265 this._isInitialRender = true;
1266 this._lastPosition = null;
1267 this._resizeSubscription.unsubscribe();
1268 this._resizeSubscription = this._viewportRuler.change().subscribe(() => {
1269 // When the window is resized, we want to trigger the next reposition as if it
1270 // was an initial render, in order for the strategy to pick a new optimal position,
1271 // otherwise position locking will cause it to stay at the old one.
1272 this._isInitialRender = true;
1273 this.apply();
1274 });
1275 }
1276 /**
1277 * Updates the position of the overlay element, using whichever preferred position relative
1278 * to the origin best fits on-screen.
1279 *
1280 * The selection of a position goes as follows:
1281 * - If any positions fit completely within the viewport as-is,
1282 * choose the first position that does so.
1283 * - If flexible dimensions are enabled and at least one satifies the given minimum width/height,
1284 * choose the position with the greatest available size modified by the positions' weight.
1285 * - If pushing is enabled, take the position that went off-screen the least and push it
1286 * on-screen.
1287 * - If none of the previous criteria were met, use the position that goes off-screen the least.
1288 * @docs-private
1289 */
1290 apply() {
1291 // We shouldn't do anything if the strategy was disposed or we're on the server.
1292 if (this._isDisposed || !this._platform.isBrowser) {
1293 return;
1294 }
1295 // If the position has been applied already (e.g. when the overlay was opened) and the
1296 // consumer opted into locking in the position, re-use the old position, in order to
1297 // prevent the overlay from jumping around.
1298 if (!this._isInitialRender && this._positionLocked && this._lastPosition) {
1299 this.reapplyLastPosition();
1300 return;
1301 }
1302 this._clearPanelClasses();
1303 this._resetOverlayElementStyles();
1304 this._resetBoundingBoxStyles();
1305 // We need the bounding rects for the origin and the overlay to determine how to position
1306 // the overlay relative to the origin.
1307 // We use the viewport rect to determine whether a position would go off-screen.
1308 this._viewportRect = this._getNarrowedViewportRect();
1309 this._originRect = this._getOriginRect();
1310 this._overlayRect = this._pane.getBoundingClientRect();
1311 const originRect = this._originRect;
1312 const overlayRect = this._overlayRect;
1313 const viewportRect = this._viewportRect;
1314 // Positions where the overlay will fit with flexible dimensions.
1315 const flexibleFits = [];
1316 // Fallback if none of the preferred positions fit within the viewport.
1317 let fallback;
1318 // Go through each of the preferred positions looking for a good fit.
1319 // If a good fit is found, it will be applied immediately.
1320 for (let pos of this._preferredPositions) {
1321 // Get the exact (x, y) coordinate for the point-of-origin on the origin element.
1322 let originPoint = this._getOriginPoint(originRect, pos);
1323 // From that point-of-origin, get the exact (x, y) coordinate for the top-left corner of the
1324 // overlay in this position. We use the top-left corner for calculations and later translate
1325 // this into an appropriate (top, left, bottom, right) style.
1326 let overlayPoint = this._getOverlayPoint(originPoint, overlayRect, pos);
1327 // Calculate how well the overlay would fit into the viewport with this point.
1328 let overlayFit = this._getOverlayFit(overlayPoint, overlayRect, viewportRect, pos);
1329 // If the overlay, without any further work, fits into the viewport, use this position.
1330 if (overlayFit.isCompletelyWithinViewport) {
1331 this._isPushed = false;
1332 this._applyPosition(pos, originPoint);
1333 return;
1334 }
1335 // If the overlay has flexible dimensions, we can use this position
1336 // so long as there's enough space for the minimum dimensions.
1337 if (this._canFitWithFlexibleDimensions(overlayFit, overlayPoint, viewportRect)) {
1338 // Save positions where the overlay will fit with flexible dimensions. We will use these
1339 // if none of the positions fit *without* flexible dimensions.
1340 flexibleFits.push({
1341 position: pos,
1342 origin: originPoint,
1343 overlayRect,
1344 boundingBoxRect: this._calculateBoundingBoxRect(originPoint, pos)
1345 });
1346 continue;
1347 }
1348 // If the current preferred position does not fit on the screen, remember the position
1349 // if it has more visible area on-screen than we've seen and move onto the next preferred
1350 // position.
1351 if (!fallback || fallback.overlayFit.visibleArea < overlayFit.visibleArea) {
1352 fallback = { overlayFit, overlayPoint, originPoint, position: pos, overlayRect };
1353 }
1354 }
1355 // If there are any positions where the overlay would fit with flexible dimensions, choose the
1356 // one that has the greatest area available modified by the position's weight
1357 if (flexibleFits.length) {
1358 let bestFit = null;
1359 let bestScore = -1;
1360 for (const fit of flexibleFits) {
1361 const score = fit.boundingBoxRect.width * fit.boundingBoxRect.height * (fit.position.weight || 1);
1362 if (score > bestScore) {
1363 bestScore = score;
1364 bestFit = fit;
1365 }
1366 }
1367 this._isPushed = false;
1368 this._applyPosition(bestFit.position, bestFit.origin);
1369 return;
1370 }
1371 // When none of the preferred positions fit within the viewport, take the position
1372 // that went off-screen the least and attempt to push it on-screen.
1373 if (this._canPush) {
1374 // TODO(jelbourn): after pushing, the opening "direction" of the overlay might not make sense.
1375 this._isPushed = true;
1376 this._applyPosition(fallback.position, fallback.originPoint);
1377 return;
1378 }
1379 // All options for getting the overlay within the viewport have been exhausted, so go with the
1380 // position that went off-screen the least.
1381 this._applyPosition(fallback.position, fallback.originPoint);
1382 }
1383 detach() {
1384 this._clearPanelClasses();
1385 this._lastPosition = null;
1386 this._previousPushAmount = null;
1387 this._resizeSubscription.unsubscribe();
1388 }
1389 /** Cleanup after the element gets destroyed. */
1390 dispose() {
1391 if (this._isDisposed) {
1392 return;
1393 }
1394 // We can't use `_resetBoundingBoxStyles` here, because it resets
1395 // some properties to zero, rather than removing them.
1396 if (this._boundingBox) {
1397 extendStyles(this._boundingBox.style, {
1398 top: '',
1399 left: '',
1400 right: '',
1401 bottom: '',
1402 height: '',
1403 width: '',
1404 alignItems: '',
1405 justifyContent: '',
1406 });
1407 }
1408 if (this._pane) {
1409 this._resetOverlayElementStyles();
1410 }
1411 if (this._overlayRef) {
1412 this._overlayRef.hostElement.classList.remove(boundingBoxClass);
1413 }
1414 this.detach();
1415 this._positionChanges.complete();
1416 this._overlayRef = this._boundingBox = null;
1417 this._isDisposed = true;
1418 }
1419 /**
1420 * This re-aligns the overlay element with the trigger in its last calculated position,
1421 * even if a position higher in the "preferred positions" list would now fit. This
1422 * allows one to re-align the panel without changing the orientation of the panel.
1423 */
1424 reapplyLastPosition() {
1425 if (!this._isDisposed && (!this._platform || this._platform.isBrowser)) {
1426 this._originRect = this._getOriginRect();
1427 this._overlayRect = this._pane.getBoundingClientRect();
1428 this._viewportRect = this._getNarrowedViewportRect();
1429 const lastPosition = this._lastPosition || this._preferredPositions[0];
1430 const originPoint = this._getOriginPoint(this._originRect, lastPosition);
1431 this._applyPosition(lastPosition, originPoint);
1432 }
1433 }
1434 /**
1435 * Sets the list of Scrollable containers that host the origin element so that
1436 * on reposition we can evaluate if it or the overlay has been clipped or outside view. Every
1437 * Scrollable must be an ancestor element of the strategy's origin element.
1438 */
1439 withScrollableContainers(scrollables) {
1440 this._scrollables = scrollables;
1441 return this;
1442 }
1443 /**
1444 * Adds new preferred positions.
1445 * @param positions List of positions options for this overlay.
1446 */
1447 withPositions(positions) {
1448 this._preferredPositions = positions;
1449 // If the last calculated position object isn't part of the positions anymore, clear
1450 // it in order to avoid it being picked up if the consumer tries to re-apply.
1451 if (positions.indexOf(this._lastPosition) === -1) {
1452 this._lastPosition = null;
1453 }
1454 this._validatePositions();
1455 return this;
1456 }
1457 /**
1458 * Sets a minimum distance the overlay may be positioned to the edge of the viewport.
1459 * @param margin Required margin between the overlay and the viewport edge in pixels.
1460 */
1461 withViewportMargin(margin) {
1462 this._viewportMargin = margin;
1463 return this;
1464 }
1465 /** Sets whether the overlay's width and height can be constrained to fit within the viewport. */
1466 withFlexibleDimensions(flexibleDimensions = true) {
1467 this._hasFlexibleDimensions = flexibleDimensions;
1468 return this;
1469 }
1470 /** Sets whether the overlay can grow after the initial open via flexible width/height. */
1471 withGrowAfterOpen(growAfterOpen = true) {
1472 this._growAfterOpen = growAfterOpen;
1473 return this;
1474 }
1475 /** Sets whether the overlay can be pushed on-screen if none of the provided positions fit. */
1476 withPush(canPush = true) {
1477 this._canPush = canPush;
1478 return this;
1479 }
1480 /**
1481 * Sets whether the overlay's position should be locked in after it is positioned
1482 * initially. When an overlay is locked in, it won't attempt to reposition itself
1483 * when the position is re-applied (e.g. when the user scrolls away).
1484 * @param isLocked Whether the overlay should locked in.
1485 */
1486 withLockedPosition(isLocked = true) {
1487 this._positionLocked = isLocked;
1488 return this;
1489 }
1490 /**
1491 * Sets the origin, relative to which to position the overlay.
1492 * Using an element origin is useful for building components that need to be positioned
1493 * relatively to a trigger (e.g. dropdown menus or tooltips), whereas using a point can be
1494 * used for cases like contextual menus which open relative to the user's pointer.
1495 * @param origin Reference to the new origin.
1496 */
1497 setOrigin(origin) {
1498 this._origin = origin;
1499 return this;
1500 }
1501 /**
1502 * Sets the default offset for the overlay's connection point on the x-axis.
1503 * @param offset New offset in the X axis.
1504 */
1505 withDefaultOffsetX(offset) {
1506 this._offsetX = offset;
1507 return this;
1508 }
1509 /**
1510 * Sets the default offset for the overlay's connection point on the y-axis.
1511 * @param offset New offset in the Y axis.
1512 */
1513 withDefaultOffsetY(offset) {
1514 this._offsetY = offset;
1515 return this;
1516 }
1517 /**
1518 * Configures that the position strategy should set a `transform-origin` on some elements
1519 * inside the overlay, depending on the current position that is being applied. This is
1520 * useful for the cases where the origin of an animation can change depending on the
1521 * alignment of the overlay.
1522 * @param selector CSS selector that will be used to find the target
1523 * elements onto which to set the transform origin.
1524 */
1525 withTransformOriginOn(selector) {
1526 this._transformOriginSelector = selector;
1527 return this;
1528 }
1529 /**
1530 * Gets the (x, y) coordinate of a connection point on the origin based on a relative position.
1531 */
1532 _getOriginPoint(originRect, pos) {
1533 let x;
1534 if (pos.originX == 'center') {
1535 // Note: when centering we should always use the `left`
1536 // offset, otherwise the position will be wrong in RTL.
1537 x = originRect.left + (originRect.width / 2);
1538 }
1539 else {
1540 const startX = this._isRtl() ? originRect.right : originRect.left;
1541 const endX = this._isRtl() ? originRect.left : originRect.right;
1542 x = pos.originX == 'start' ? startX : endX;
1543 }
1544 let y;
1545 if (pos.originY == 'center') {
1546 y = originRect.top + (originRect.height / 2);
1547 }
1548 else {
1549 y = pos.originY == 'top' ? originRect.top : originRect.bottom;
1550 }
1551 return { x, y };
1552 }
1553 /**
1554 * Gets the (x, y) coordinate of the top-left corner of the overlay given a given position and
1555 * origin point to which the overlay should be connected.
1556 */
1557 _getOverlayPoint(originPoint, overlayRect, pos) {
1558 // Calculate the (overlayStartX, overlayStartY), the start of the
1559 // potential overlay position relative to the origin point.
1560 let overlayStartX;
1561 if (pos.overlayX == 'center') {
1562 overlayStartX = -overlayRect.width / 2;
1563 }
1564 else if (pos.overlayX === 'start') {
1565 overlayStartX = this._isRtl() ? -overlayRect.width : 0;
1566 }
1567 else {
1568 overlayStartX = this._isRtl() ? 0 : -overlayRect.width;
1569 }
1570 let overlayStartY;
1571 if (pos.overlayY == 'center') {
1572 overlayStartY = -overlayRect.height / 2;
1573 }
1574 else {
1575 overlayStartY = pos.overlayY == 'top' ? 0 : -overlayRect.height;
1576 }
1577 // The (x, y) coordinates of the overlay.
1578 return {
1579 x: originPoint.x + overlayStartX,
1580 y: originPoint.y + overlayStartY,
1581 };
1582 }
1583 /** Gets how well an overlay at the given point will fit within the viewport. */
1584 _getOverlayFit(point, rawOverlayRect, viewport, position) {
1585 // Round the overlay rect when comparing against the
1586 // viewport, because the viewport is always rounded.
1587 const overlay = getRoundedBoundingClientRect(rawOverlayRect);
1588 let { x, y } = point;
1589 let offsetX = this._getOffset(position, 'x');
1590 let offsetY = this._getOffset(position, 'y');
1591 // Account for the offsets since they could push the overlay out of the viewport.
1592 if (offsetX) {
1593 x += offsetX;
1594 }
1595 if (offsetY) {
1596 y += offsetY;
1597 }
1598 // How much the overlay would overflow at this position, on each side.
1599 let leftOverflow = 0 - x;
1600 let rightOverflow = (x + overlay.width) - viewport.width;
1601 let topOverflow = 0 - y;
1602 let bottomOverflow = (y + overlay.height) - viewport.height;
1603 // Visible parts of the element on each axis.
1604 let visibleWidth = this._subtractOverflows(overlay.width, leftOverflow, rightOverflow);
1605 let visibleHeight = this._subtractOverflows(overlay.height, topOverflow, bottomOverflow);
1606 let visibleArea = visibleWidth * visibleHeight;
1607 return {
1608 visibleArea,
1609 isCompletelyWithinViewport: (overlay.width * overlay.height) === visibleArea,
1610 fitsInViewportVertically: visibleHeight === overlay.height,
1611 fitsInViewportHorizontally: visibleWidth == overlay.width,
1612 };
1613 }
1614 /**
1615 * Whether the overlay can fit within the viewport when it may resize either its width or height.
1616 * @param fit How well the overlay fits in the viewport at some position.
1617 * @param point The (x, y) coordinates of the overlat at some position.
1618 * @param viewport The geometry of the viewport.
1619 */
1620 _canFitWithFlexibleDimensions(fit, point, viewport) {
1621 if (this._hasFlexibleDimensions) {
1622 const availableHeight = viewport.bottom - point.y;
1623 const availableWidth = viewport.right - point.x;
1624 const minHeight = getPixelValue(this._overlayRef.getConfig().minHeight);
1625 const minWidth = getPixelValue(this._overlayRef.getConfig().minWidth);
1626 const verticalFit = fit.fitsInViewportVertically ||
1627 (minHeight != null && minHeight <= availableHeight);
1628 const horizontalFit = fit.fitsInViewportHorizontally ||
1629 (minWidth != null && minWidth <= availableWidth);
1630 return verticalFit && horizontalFit;
1631 }
1632 return false;
1633 }
1634 /**
1635 * Gets the point at which the overlay can be "pushed" on-screen. If the overlay is larger than
1636 * the viewport, the top-left corner will be pushed on-screen (with overflow occuring on the
1637 * right and bottom).
1638 *
1639 * @param start Starting point from which the overlay is pushed.
1640 * @param overlay Dimensions of the overlay.
1641 * @param scrollPosition Current viewport scroll position.
1642 * @returns The point at which to position the overlay after pushing. This is effectively a new
1643 * originPoint.
1644 */
1645 _pushOverlayOnScreen(start, rawOverlayRect, scrollPosition) {
1646 // If the position is locked and we've pushed the overlay already, reuse the previous push
1647 // amount, rather than pushing it again. If we were to continue pushing, the element would
1648 // remain in the viewport, which goes against the expectations when position locking is enabled.
1649 if (this._previousPushAmount && this._positionLocked) {
1650 return {
1651 x: start.x + this._previousPushAmount.x,
1652 y: start.y + this._previousPushAmount.y
1653 };
1654 }
1655 // Round the overlay rect when comparing against the
1656 // viewport, because the viewport is always rounded.
1657 const overlay = getRoundedBoundingClientRect(rawOverlayRect);
1658 const viewport = this._viewportRect;
1659 // Determine how much the overlay goes outside the viewport on each
1660 // side, which we'll use to decide which direction to push it.
1661 const overflowRight = Math.max(start.x + overlay.width - viewport.width, 0);
1662 const overflowBottom = Math.max(start.y + overlay.height - viewport.height, 0);
1663 const overflowTop = Math.max(viewport.top - scrollPosition.top - start.y, 0);
1664 const overflowLeft = Math.max(viewport.left - scrollPosition.left - start.x, 0);
1665 // Amount by which to push the overlay in each axis such that it remains on-screen.
1666 let pushX = 0;
1667 let pushY = 0;
1668 // If the overlay fits completely within the bounds of the viewport, push it from whichever
1669 // direction is goes off-screen. Otherwise, push the top-left corner such that its in the
1670 // viewport and allow for the trailing end of the overlay to go out of bounds.
1671 if (overlay.width <= viewport.width) {
1672 pushX = overflowLeft || -overflowRight;
1673 }
1674 else {
1675 pushX = start.x < this._viewportMargin ? (viewport.left - scrollPosition.left) - start.x : 0;
1676 }
1677 if (overlay.height <= viewport.height) {
1678 pushY = overflowTop || -overflowBottom;
1679 }
1680 else {
1681 pushY = start.y < this._viewportMargin ? (viewport.top - scrollPosition.top) - start.y : 0;
1682 }
1683 this._previousPushAmount = { x: pushX, y: pushY };
1684 return {
1685 x: start.x + pushX,
1686 y: start.y + pushY,
1687 };
1688 }
1689 /**
1690 * Applies a computed position to the overlay and emits a position change.
1691 * @param position The position preference
1692 * @param originPoint The point on the origin element where the overlay is connected.
1693 */
1694 _applyPosition(position, originPoint) {
1695 this._setTransformOrigin(position);
1696 this._setOverlayElementStyles(originPoint, position);
1697 this._setBoundingBoxStyles(originPoint, position);
1698 if (position.panelClass) {
1699 this._addPanelClasses(position.panelClass);
1700 }
1701 // Save the last connected position in case the position needs to be re-calculated.
1702 this._lastPosition = position;
1703 // Notify that the position has been changed along with its change properties.
1704 // We only emit if we've got any subscriptions, because the scroll visibility
1705 // calculcations can be somewhat expensive.
1706 if (this._positionChanges.observers.length) {
1707 const scrollableViewProperties = this._getScrollVisibility();
1708 const changeEvent = new ConnectedOverlayPositionChange(position, scrollableViewProperties);
1709 this._positionChanges.next(changeEvent);
1710 }
1711 this._isInitialRender = false;
1712 }
1713 /** Sets the transform origin based on the configured selector and the passed-in position. */
1714 _setTransformOrigin(position) {
1715 if (!this._transformOriginSelector) {
1716 return;
1717 }
1718 const elements = this._boundingBox.querySelectorAll(this._transformOriginSelector);
1719 let xOrigin;
1720 let yOrigin = position.overlayY;
1721 if (position.overlayX === 'center') {
1722 xOrigin = 'center';
1723 }
1724 else if (this._isRtl()) {
1725 xOrigin = position.overlayX === 'start' ? 'right' : 'left';
1726 }
1727 else {
1728 xOrigin = position.overlayX === 'start' ? 'left' : 'right';
1729 }
1730 for (let i = 0; i < elements.length; i++) {
1731 elements[i].style.transformOrigin = `${xOrigin} ${yOrigin}`;
1732 }
1733 }
1734 /**
1735 * Gets the position and size of the overlay's sizing container.
1736 *
1737 * This method does no measuring and applies no styles so that we can cheaply compute the
1738 * bounds for all positions and choose the best fit based on these results.
1739 */
1740 _calculateBoundingBoxRect(origin, position) {
1741 const viewport = this._viewportRect;
1742 const isRtl = this._isRtl();
1743 let height, top, bottom;
1744 if (position.overlayY === 'top') {
1745 // Overlay is opening "downward" and thus is bound by the bottom viewport edge.
1746 top = origin.y;
1747 height = viewport.height - top + this._viewportMargin;
1748 }
1749 else if (position.overlayY === 'bottom') {
1750 // Overlay is opening "upward" and thus is bound by the top viewport edge. We need to add
1751 // the viewport margin back in, because the viewport rect is narrowed down to remove the
1752 // margin, whereas the `origin` position is calculated based on its `ClientRect`.
1753 bottom = viewport.height - origin.y + this._viewportMargin * 2;
1754 height = viewport.height - bottom + this._viewportMargin;
1755 }
1756 else {
1757 // If neither top nor bottom, it means that the overlay is vertically centered on the
1758 // origin point. Note that we want the position relative to the viewport, rather than
1759 // the page, which is why we don't use something like `viewport.bottom - origin.y` and
1760 // `origin.y - viewport.top`.
1761 const smallestDistanceToViewportEdge = Math.min(viewport.bottom - origin.y + viewport.top, origin.y);
1762 const previousHeight = this._lastBoundingBoxSize.height;
1763 height = smallestDistanceToViewportEdge * 2;
1764 top = origin.y - smallestDistanceToViewportEdge;
1765 if (height > previousHeight && !this._isInitialRender && !this._growAfterOpen) {
1766 top = origin.y - (previousHeight / 2);
1767 }
1768 }
1769 // The overlay is opening 'right-ward' (the content flows to the right).
1770 const isBoundedByRightViewportEdge = (position.overlayX === 'start' && !isRtl) ||
1771 (position.overlayX === 'end' && isRtl);
1772 // The overlay is opening 'left-ward' (the content flows to the left).
1773 const isBoundedByLeftViewportEdge = (position.overlayX === 'end' && !isRtl) ||
1774 (position.overlayX === 'start' && isRtl);
1775 let width, left, right;
1776 if (isBoundedByLeftViewportEdge) {
1777 right = viewport.width - origin.x + this._viewportMargin;
1778 width = origin.x - this._viewportMargin;
1779 }
1780 else if (isBoundedByRightViewportEdge) {
1781 left = origin.x;
1782 width = viewport.right - origin.x;
1783 }
1784 else {
1785 // If neither start nor end, it means that the overlay is horizontally centered on the
1786 // origin point. Note that we want the position relative to the viewport, rather than
1787 // the page, which is why we don't use something like `viewport.right - origin.x` and
1788 // `origin.x - viewport.left`.
1789 const smallestDistanceToViewportEdge = Math.min(viewport.right - origin.x + viewport.left, origin.x);
1790 const previousWidth = this._lastBoundingBoxSize.width;
1791 width = smallestDistanceToViewportEdge * 2;
1792 left = origin.x - smallestDistanceToViewportEdge;
1793 if (width > previousWidth && !this._isInitialRender && !this._growAfterOpen) {
1794 left = origin.x - (previousWidth / 2);
1795 }
1796 }
1797 return { top: top, left: left, bottom: bottom, right: right, width, height };
1798 }
1799 /**
1800 * Sets the position and size of the overlay's sizing wrapper. The wrapper is positioned on the
1801 * origin's connection point and stetches to the bounds of the viewport.
1802 *
1803 * @param origin The point on the origin element where the overlay is connected.
1804 * @param position The position preference
1805 */
1806 _setBoundingBoxStyles(origin, position) {
1807 const boundingBoxRect = this._calculateBoundingBoxRect(origin, position);
1808 // It's weird if the overlay *grows* while scrolling, so we take the last size into account
1809 // when applying a new size.
1810 if (!this._isInitialRender && !this._growAfterOpen) {
1811 boundingBoxRect.height = Math.min(boundingBoxRect.height, this._lastBoundingBoxSize.height);
1812 boundingBoxRect.width = Math.min(boundingBoxRect.width, this._lastBoundingBoxSize.width);
1813 }
1814 const styles = {};
1815 if (this._hasExactPosition()) {
1816 styles.top = styles.left = '0';
1817 styles.bottom = styles.right = styles.maxHeight = styles.maxWidth = '';
1818 styles.width = styles.height = '100%';
1819 }
1820 else {
1821 const maxHeight = this._overlayRef.getConfig().maxHeight;
1822 const maxWidth = this._overlayRef.getConfig().maxWidth;
1823 styles.height = coerceCssPixelValue(boundingBoxRect.height);
1824 styles.top = coerceCssPixelValue(boundingBoxRect.top);
1825 styles.bottom = coerceCssPixelValue(boundingBoxRect.bottom);
1826 styles.width = coerceCssPixelValue(boundingBoxRect.width);
1827 styles.left = coerceCssPixelValue(boundingBoxRect.left);
1828 styles.right = coerceCssPixelValue(boundingBoxRect.right);
1829 // Push the pane content towards the proper direction.
1830 if (position.overlayX === 'center') {
1831 styles.alignItems = 'center';
1832 }
1833 else {
1834 styles.alignItems = position.overlayX === 'end' ? 'flex-end' : 'flex-start';
1835 }
1836 if (position.overlayY === 'center') {
1837 styles.justifyContent = 'center';
1838 }
1839 else {
1840 styles.justifyContent = position.overlayY === 'bottom' ? 'flex-end' : 'flex-start';
1841 }
1842 if (maxHeight) {
1843 styles.maxHeight = coerceCssPixelValue(maxHeight);
1844 }
1845 if (maxWidth) {
1846 styles.maxWidth = coerceCssPixelValue(maxWidth);
1847 }
1848 }
1849 this._lastBoundingBoxSize = boundingBoxRect;
1850 extendStyles(this._boundingBox.style, styles);
1851 }
1852 /** Resets the styles for the bounding box so that a new positioning can be computed. */
1853 _resetBoundingBoxStyles() {
1854 extendStyles(this._boundingBox.style, {
1855 top: '0',
1856 left: '0',
1857 right: '0',
1858 bottom: '0',
1859 height: '',
1860 width: '',
1861 alignItems: '',
1862 justifyContent: '',
1863 });
1864 }
1865 /** Resets the styles for the overlay pane so that a new positioning can be computed. */
1866 _resetOverlayElementStyles() {
1867 extendStyles(this._pane.style, {
1868 top: '',
1869 left: '',
1870 bottom: '',
1871 right: '',
1872 position: '',
1873 transform: '',
1874 });
1875 }
1876 /** Sets positioning styles to the overlay element. */
1877 _setOverlayElementStyles(originPoint, position) {
1878 const styles = {};
1879 const hasExactPosition = this._hasExactPosition();
1880 const hasFlexibleDimensions = this._hasFlexibleDimensions;
1881 const config = this._overlayRef.getConfig();
1882 if (hasExactPosition) {
1883 const scrollPosition = this._viewportRuler.getViewportScrollPosition();
1884 extendStyles(styles, this._getExactOverlayY(position, originPoint, scrollPosition));
1885 extendStyles(styles, this._getExactOverlayX(position, originPoint, scrollPosition));
1886 }
1887 else {
1888 styles.position = 'static';
1889 }
1890 // Use a transform to apply the offsets. We do this because the `center` positions rely on
1891 // being in the normal flex flow and setting a `top` / `left` at all will completely throw
1892 // off the position. We also can't use margins, because they won't have an effect in some
1893 // cases where the element doesn't have anything to "push off of". Finally, this works
1894 // better both with flexible and non-flexible positioning.
1895 let transformString = '';
1896 let offsetX = this._getOffset(position, 'x');
1897 let offsetY = this._getOffset(position, 'y');
1898 if (offsetX) {
1899 transformString += `translateX(${offsetX}px) `;
1900 }
1901 if (offsetY) {
1902 transformString += `translateY(${offsetY}px)`;
1903 }
1904 styles.transform = transformString.trim();
1905 // If a maxWidth or maxHeight is specified on the overlay, we remove them. We do this because
1906 // we need these values to both be set to "100%" for the automatic flexible sizing to work.
1907 // The maxHeight and maxWidth are set on the boundingBox in order to enforce the constraint.
1908 // Note that this doesn't apply when we have an exact position, in which case we do want to
1909 // apply them because they'll be cleared from the bounding box.
1910 if (config.maxHeight) {
1911 if (hasExactPosition) {
1912 styles.maxHeight = coerceCssPixelValue(config.maxHeight);
1913 }
1914 else if (hasFlexibleDimensions) {
1915 styles.maxHeight = '';
1916 }
1917 }
1918 if (config.maxWidth) {
1919 if (hasExactPosition) {
1920 styles.maxWidth = coerceCssPixelValue(config.maxWidth);
1921 }
1922 else if (hasFlexibleDimensions) {
1923 styles.maxWidth = '';
1924 }
1925 }
1926 extendStyles(this._pane.style, styles);
1927 }
1928 /** Gets the exact top/bottom for the overlay when not using flexible sizing or when pushing. */
1929 _getExactOverlayY(position, originPoint, scrollPosition) {
1930 // Reset any existing styles. This is necessary in case the
1931 // preferred position has changed since the last `apply`.
1932 let styles = { top: '', bottom: '' };
1933 let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position);
1934 if (this._isPushed) {
1935 overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect, scrollPosition);
1936 }
1937 let virtualKeyboardOffset = this._overlayContainer.getContainerElement().getBoundingClientRect().top;
1938 // Normally this would be zero, however when the overlay is attached to an input (e.g. in an
1939 // autocomplete), mobile browsers will shift everything in order to put the input in the middle
1940 // of the screen and to make space for the virtual keyboard. We need to account for this offset,
1941 // otherwise our positioning will be thrown off.
1942 overlayPoint.y -= virtualKeyboardOffset;
1943 // We want to set either `top` or `bottom` based on whether the overlay wants to appear
1944 // above or below the origin and the direction in which the element will expand.
1945 if (position.overlayY === 'bottom') {
1946 // When using `bottom`, we adjust the y position such that it is the distance
1947 // from the bottom of the viewport rather than the top.
1948 const documentHeight = this._document.documentElement.clientHeight;
1949 styles.bottom = `${documentHeight - (overlayPoint.y + this._overlayRect.height)}px`;
1950 }
1951 else {
1952 styles.top = coerceCssPixelValue(overlayPoint.y);
1953 }
1954 return styles;
1955 }
1956 /** Gets the exact left/right for the overlay when not using flexible sizing or when pushing. */
1957 _getExactOverlayX(position, originPoint, scrollPosition) {
1958 // Reset any existing styles. This is necessary in case the preferred position has
1959 // changed since the last `apply`.
1960 let styles = { left: '', right: '' };
1961 let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position);
1962 if (this._isPushed) {
1963 overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect, scrollPosition);
1964 }
1965 // We want to set either `left` or `right` based on whether the overlay wants to appear "before"
1966 // or "after" the origin, which determines the direction in which the element will expand.
1967 // For the horizontal axis, the meaning of "before" and "after" change based on whether the
1968 // page is in RTL or LTR.
1969 let horizontalStyleProperty;
1970 if (this._isRtl()) {
1971 horizontalStyleProperty = position.overlayX === 'end' ? 'left' : 'right';
1972 }
1973 else {
1974 horizontalStyleProperty = position.overlayX === 'end' ? 'right' : 'left';
1975 }
1976 // When we're setting `right`, we adjust the x position such that it is the distance
1977 // from the right edge of the viewport rather than the left edge.
1978 if (horizontalStyleProperty === 'right') {
1979 const documentWidth = this._document.documentElement.clientWidth;
1980 styles.right = `${documentWidth - (overlayPoint.x + this._overlayRect.width)}px`;
1981 }
1982 else {
1983 styles.left = coerceCssPixelValue(overlayPoint.x);
1984 }
1985 return styles;
1986 }
1987 /**
1988 * Gets the view properties of the trigger and overlay, including whether they are clipped
1989 * or completely outside the view of any of the strategy's scrollables.
1990 */
1991 _getScrollVisibility() {
1992 // Note: needs fresh rects since the position could've changed.
1993 const originBounds = this._getOriginRect();
1994 const overlayBounds = this._pane.getBoundingClientRect();
1995 // TODO(jelbourn): instead of needing all of the client rects for these scrolling containers
1996 // every time, we should be able to use the scrollTop of the containers if the size of those
1997 // containers hasn't changed.
1998 const scrollContainerBounds = this._scrollables.map(scrollable => {
1999 return scrollable.getElementRef().nativeElement.getBoundingClientRect();
2000 });
2001 return {
2002 isOriginClipped: isElementClippedByScrolling(originBounds, scrollContainerBounds),
2003 isOriginOutsideView: isElementScrolledOutsideView(originBounds, scrollContainerBounds),
2004 isOverlayClipped: isElementClippedByScrolling(overlayBounds, scrollContainerBounds),
2005 isOverlayOutsideView: isElementScrolledOutsideView(overlayBounds, scrollContainerBounds),
2006 };
2007 }
2008 /** Subtracts the amount that an element is overflowing on an axis from its length. */
2009 _subtractOverflows(length, ...overflows) {
2010 return overflows.reduce((currentValue, currentOverflow) => {
2011 return currentValue - Math.max(currentOverflow, 0);
2012 }, length);
2013 }
2014 /** Narrows the given viewport rect by the current _viewportMargin. */
2015 _getNarrowedViewportRect() {
2016 // We recalculate the viewport rect here ourselves, rather than using the ViewportRuler,
2017 // because we want to use the `clientWidth` and `clientHeight` as the base. The difference
2018 // being that the client properties don't include the scrollbar, as opposed to `innerWidth`
2019 // and `innerHeight` that do. This is necessary, because the overlay container uses
2020 // 100% `width` and `height` which don't include the scrollbar either.
2021 const width = this._document.documentElement.clientWidth;
2022 const height = this._document.documentElement.clientHeight;
2023 const scrollPosition = this._viewportRuler.getViewportScrollPosition();
2024 return {
2025 top: scrollPosition.top + this._viewportMargin,
2026 left: scrollPosition.left + this._viewportMargin,
2027 right: scrollPosition.left + width - this._viewportMargin,
2028 bottom: scrollPosition.top + height - this._viewportMargin,
2029 width: width - (2 * this._viewportMargin),
2030 height: height - (2 * this._viewportMargin),
2031 };
2032 }
2033 /** Whether the we're dealing with an RTL context */
2034 _isRtl() {
2035 return this._overlayRef.getDirection() === 'rtl';
2036 }
2037 /** Determines whether the overlay uses exact or flexible positioning. */
2038 _hasExactPosition() {
2039 return !this._hasFlexibleDimensions || this._isPushed;
2040 }
2041 /** Retrieves the offset of a position along the x or y axis. */
2042 _getOffset(position, axis) {
2043 if (axis === 'x') {
2044 // We don't do something like `position['offset' + axis]` in
2045 // order to avoid breking minifiers that rename properties.
2046 return position.offsetX == null ? this._offsetX : position.offsetX;
2047 }
2048 return position.offsetY == null ? this._offsetY : position.offsetY;
2049 }
2050 /** Validates that the current position match the expected values. */
2051 _validatePositions() {
2052 if (typeof ngDevMode === 'undefined' || ngDevMode) {
2053 if (!this._preferredPositions.length) {
2054 throw Error('FlexibleConnectedPositionStrategy: At least one position is required.');
2055 }
2056 // TODO(crisbeto): remove these once Angular's template type
2057 // checking is advanced enough to catch these cases.
2058 this._preferredPositions.forEach(pair => {
2059 validateHorizontalPosition('originX', pair.originX);
2060 validateVerticalPosition('originY', pair.originY);
2061 validateHorizontalPosition('overlayX', pair.overlayX);
2062 validateVerticalPosition('overlayY', pair.overlayY);
2063 });
2064 }
2065 }
2066 /** Adds a single CSS class or an array of classes on the overlay panel. */
2067 _addPanelClasses(cssClasses) {
2068 if (this._pane) {
2069 coerceArray(cssClasses).forEach(cssClass => {
2070 if (cssClass !== '' && this._appliedPanelClasses.indexOf(cssClass) === -1) {
2071 this._appliedPanelClasses.push(cssClass);
2072 this._pane.classList.add(cssClass);
2073 }
2074 });
2075 }
2076 }
2077 /** Clears the classes that the position strategy has applied from the overlay panel. */
2078 _clearPanelClasses() {
2079 if (this._pane) {
2080 this._appliedPanelClasses.forEach(cssClass => {
2081 this._pane.classList.remove(cssClass);
2082 });
2083 this._appliedPanelClasses = [];
2084 }
2085 }
2086 /** Returns the ClientRect of the current origin. */
2087 _getOriginRect() {
2088 const origin = this._origin;
2089 if (origin instanceof ElementRef) {
2090 return origin.nativeElement.getBoundingClientRect();
2091 }
2092 // Check for Element so SVG elements are also supported.
2093 if (origin instanceof Element) {
2094 return origin.getBoundingClientRect();
2095 }
2096 const width = origin.width || 0;
2097 const height = origin.height || 0;
2098 // If the origin is a point, return a client rect as if it was a 0x0 element at the point.
2099 return {
2100 top: origin.y,
2101 bottom: origin.y + height,
2102 left: origin.x,
2103 right: origin.x + width,
2104 height,
2105 width
2106 };
2107 }
2108}
2109/** Shallow-extends a stylesheet object with another stylesheet object. */
2110function extendStyles(destination, source) {
2111 for (let key in source) {
2112 if (source.hasOwnProperty(key)) {
2113 destination[key] = source[key];
2114 }
2115 }
2116 return destination;
2117}
2118/**
2119 * Extracts the pixel value as a number from a value, if it's a number
2120 * or a CSS pixel string (e.g. `1337px`). Otherwise returns null.
2121 */
2122function getPixelValue(input) {
2123 if (typeof input !== 'number' && input != null) {
2124 const [value, units] = input.split(cssUnitPattern);
2125 return (!units || units === 'px') ? parseFloat(value) : null;
2126 }
2127 return input || null;
2128}
2129/**
2130 * Gets a version of an element's bounding `ClientRect` where all the values are rounded down to
2131 * the nearest pixel. This allows us to account for the cases where there may be sub-pixel
2132 * deviations in the `ClientRect` returned by the browser (e.g. when zoomed in with a percentage
2133 * size, see #21350).
2134 */
2135function getRoundedBoundingClientRect(clientRect) {
2136 return {
2137 top: Math.floor(clientRect.top),
2138 right: Math.floor(clientRect.right),
2139 bottom: Math.floor(clientRect.bottom),
2140 left: Math.floor(clientRect.left),
2141 width: Math.floor(clientRect.width),
2142 height: Math.floor(clientRect.height)
2143 };
2144}
2145
2146/**
2147 * @license
2148 * Copyright Google LLC All Rights Reserved.
2149 *
2150 * Use of this source code is governed by an MIT-style license that can be
2151 * found in the LICENSE file at https://angular.io/license
2152 */
2153/**
2154 * A strategy for positioning overlays. Using this strategy, an overlay is given an
2155 * implicit position relative to some origin element. The relative position is defined in terms of
2156 * a point on the origin element that is connected to a point on the overlay element. For example,
2157 * a basic dropdown is connecting the bottom-left corner of the origin to the top-left corner
2158 * of the overlay.
2159 * @deprecated Use `FlexibleConnectedPositionStrategy` instead.
2160 * @breaking-change 8.0.0
2161 */
2162class ConnectedPositionStrategy {
2163 constructor(originPos, overlayPos, connectedTo, viewportRuler, document, platform, overlayContainer) {
2164 /** Ordered list of preferred positions, from most to least desirable. */
2165 this._preferredPositions = [];
2166 // Since the `ConnectedPositionStrategy` is deprecated and we don't want to maintain
2167 // the extra logic, we create an instance of the positioning strategy that has some
2168 // defaults that make it behave as the old position strategy and to which we'll
2169 // proxy all of the API calls.
2170 this._positionStrategy = new FlexibleConnectedPositionStrategy(connectedTo, viewportRuler, document, platform, overlayContainer)
2171 .withFlexibleDimensions(false)
2172 .withPush(false)
2173 .withViewportMargin(0);
2174 this.withFallbackPosition(originPos, overlayPos);
2175 this.onPositionChange = this._positionStrategy.positionChanges;
2176 }
2177 /** Ordered list of preferred positions, from most to least desirable. */
2178 get positions() {
2179 return this._preferredPositions;
2180 }
2181 /** Attach this position strategy to an overlay. */
2182 attach(overlayRef) {
2183 this._overlayRef = overlayRef;
2184 this._positionStrategy.attach(overlayRef);
2185 if (this._direction) {
2186 overlayRef.setDirection(this._direction);
2187 this._direction = null;
2188 }
2189 }
2190 /** Disposes all resources used by the position strategy. */
2191 dispose() {
2192 this._positionStrategy.dispose();
2193 }
2194 /** @docs-private */
2195 detach() {
2196 this._positionStrategy.detach();
2197 }
2198 /**
2199 * Updates the position of the overlay element, using whichever preferred position relative
2200 * to the origin fits on-screen.
2201 * @docs-private
2202 */
2203 apply() {
2204 this._positionStrategy.apply();
2205 }
2206 /**
2207 * Re-positions the overlay element with the trigger in its last calculated position,
2208 * even if a position higher in the "preferred positions" list would now fit. This
2209 * allows one to re-align the panel without changing the orientation of the panel.
2210 */
2211 recalculateLastPosition() {
2212 this._positionStrategy.reapplyLastPosition();
2213 }
2214 /**
2215 * Sets the list of Scrollable containers that host the origin element so that
2216 * on reposition we can evaluate if it or the overlay has been clipped or outside view. Every
2217 * Scrollable must be an ancestor element of the strategy's origin element.
2218 */
2219 withScrollableContainers(scrollables) {
2220 this._positionStrategy.withScrollableContainers(scrollables);
2221 }
2222 /**
2223 * Adds a new preferred fallback position.
2224 * @param originPos
2225 * @param overlayPos
2226 */
2227 withFallbackPosition(originPos, overlayPos, offsetX, offsetY) {
2228 const position = new ConnectionPositionPair(originPos, overlayPos, offsetX, offsetY);
2229 this._preferredPositions.push(position);
2230 this._positionStrategy.withPositions(this._preferredPositions);
2231 return this;
2232 }
2233 /**
2234 * Sets the layout direction so the overlay's position can be adjusted to match.
2235 * @param dir New layout direction.
2236 */
2237 withDirection(dir) {
2238 // Since the direction might be declared before the strategy is attached,
2239 // we save the value in a temporary property and we'll transfer it to the
2240 // overlay ref on attachment.
2241 if (this._overlayRef) {
2242 this._overlayRef.setDirection(dir);
2243 }
2244 else {
2245 this._direction = dir;
2246 }
2247 return this;
2248 }
2249 /**
2250 * Sets an offset for the overlay's connection point on the x-axis
2251 * @param offset New offset in the X axis.
2252 */
2253 withOffsetX(offset) {
2254 this._positionStrategy.withDefaultOffsetX(offset);
2255 return this;
2256 }
2257 /**
2258 * Sets an offset for the overlay's connection point on the y-axis
2259 * @param offset New offset in the Y axis.
2260 */
2261 withOffsetY(offset) {
2262 this._positionStrategy.withDefaultOffsetY(offset);
2263 return this;
2264 }
2265 /**
2266 * Sets whether the overlay's position should be locked in after it is positioned
2267 * initially. When an overlay is locked in, it won't attempt to reposition itself
2268 * when the position is re-applied (e.g. when the user scrolls away).
2269 * @param isLocked Whether the overlay should locked in.
2270 */
2271 withLockedPosition(isLocked) {
2272 this._positionStrategy.withLockedPosition(isLocked);
2273 return this;
2274 }
2275 /**
2276 * Overwrites the current set of positions with an array of new ones.
2277 * @param positions Position pairs to be set on the strategy.
2278 */
2279 withPositions(positions) {
2280 this._preferredPositions = positions.slice();
2281 this._positionStrategy.withPositions(this._preferredPositions);
2282 return this;
2283 }
2284 /**
2285 * Sets the origin element, relative to which to position the overlay.
2286 * @param origin Reference to the new origin element.
2287 */
2288 setOrigin(origin) {
2289 this._positionStrategy.setOrigin(origin);
2290 return this;
2291 }
2292}
2293
2294/**
2295 * @license
2296 * Copyright Google LLC All Rights Reserved.
2297 *
2298 * Use of this source code is governed by an MIT-style license that can be
2299 * found in the LICENSE file at https://angular.io/license
2300 */
2301/** Class to be added to the overlay pane wrapper. */
2302const wrapperClass = 'cdk-global-overlay-wrapper';
2303/**
2304 * A strategy for positioning overlays. Using this strategy, an overlay is given an
2305 * explicit position relative to the browser's viewport. We use flexbox, instead of
2306 * transforms, in order to avoid issues with subpixel rendering which can cause the
2307 * element to become blurry.
2308 */
2309class GlobalPositionStrategy {
2310 constructor() {
2311 this._cssPosition = 'static';
2312 this._topOffset = '';
2313 this._bottomOffset = '';
2314 this._leftOffset = '';
2315 this._rightOffset = '';
2316 this._alignItems = '';
2317 this._justifyContent = '';
2318 this._width = '';
2319 this._height = '';
2320 }
2321 attach(overlayRef) {
2322 const config = overlayRef.getConfig();
2323 this._overlayRef = overlayRef;
2324 if (this._width && !config.width) {
2325 overlayRef.updateSize({ width: this._width });
2326 }
2327 if (this._height && !config.height) {
2328 overlayRef.updateSize({ height: this._height });
2329 }
2330 overlayRef.hostElement.classList.add(wrapperClass);
2331 this._isDisposed = false;
2332 }
2333 /**
2334 * Sets the top position of the overlay. Clears any previously set vertical position.
2335 * @param value New top offset.
2336 */
2337 top(value = '') {
2338 this._bottomOffset = '';
2339 this._topOffset = value;
2340 this._alignItems = 'flex-start';
2341 return this;
2342 }
2343 /**
2344 * Sets the left position of the overlay. Clears any previously set horizontal position.
2345 * @param value New left offset.
2346 */
2347 left(value = '') {
2348 this._rightOffset = '';
2349 this._leftOffset = value;
2350 this._justifyContent = 'flex-start';
2351 return this;
2352 }
2353 /**
2354 * Sets the bottom position of the overlay. Clears any previously set vertical position.
2355 * @param value New bottom offset.
2356 */
2357 bottom(value = '') {
2358 this._topOffset = '';
2359 this._bottomOffset = value;
2360 this._alignItems = 'flex-end';
2361 return this;
2362 }
2363 /**
2364 * Sets the right position of the overlay. Clears any previously set horizontal position.
2365 * @param value New right offset.
2366 */
2367 right(value = '') {
2368 this._leftOffset = '';
2369 this._rightOffset = value;
2370 this._justifyContent = 'flex-end';
2371 return this;
2372 }
2373 /**
2374 * Sets the overlay width and clears any previously set width.
2375 * @param value New width for the overlay
2376 * @deprecated Pass the `width` through the `OverlayConfig`.
2377 * @breaking-change 8.0.0
2378 */
2379 width(value = '') {
2380 if (this._overlayRef) {
2381 this._overlayRef.updateSize({ width: value });
2382 }
2383 else {
2384 this._width = value;
2385 }
2386 return this;
2387 }
2388 /**
2389 * Sets the overlay height and clears any previously set height.
2390 * @param value New height for the overlay
2391 * @deprecated Pass the `height` through the `OverlayConfig`.
2392 * @breaking-change 8.0.0
2393 */
2394 height(value = '') {
2395 if (this._overlayRef) {
2396 this._overlayRef.updateSize({ height: value });
2397 }
2398 else {
2399 this._height = value;
2400 }
2401 return this;
2402 }
2403 /**
2404 * Centers the overlay horizontally with an optional offset.
2405 * Clears any previously set horizontal position.
2406 *
2407 * @param offset Overlay offset from the horizontal center.
2408 */
2409 centerHorizontally(offset = '') {
2410 this.left(offset);
2411 this._justifyContent = 'center';
2412 return this;
2413 }
2414 /**
2415 * Centers the overlay vertically with an optional offset.
2416 * Clears any previously set vertical position.
2417 *
2418 * @param offset Overlay offset from the vertical center.
2419 */
2420 centerVertically(offset = '') {
2421 this.top(offset);
2422 this._alignItems = 'center';
2423 return this;
2424 }
2425 /**
2426 * Apply the position to the element.
2427 * @docs-private
2428 */
2429 apply() {
2430 // Since the overlay ref applies the strategy asynchronously, it could
2431 // have been disposed before it ends up being applied. If that is the
2432 // case, we shouldn't do anything.
2433 if (!this._overlayRef || !this._overlayRef.hasAttached()) {
2434 return;
2435 }
2436 const styles = this._overlayRef.overlayElement.style;
2437 const parentStyles = this._overlayRef.hostElement.style;
2438 const config = this._overlayRef.getConfig();
2439 const { width, height, maxWidth, maxHeight } = config;
2440 const shouldBeFlushHorizontally = (width === '100%' || width === '100vw') &&
2441 (!maxWidth || maxWidth === '100%' || maxWidth === '100vw');
2442 const shouldBeFlushVertically = (height === '100%' || height === '100vh') &&
2443 (!maxHeight || maxHeight === '100%' || maxHeight === '100vh');
2444 styles.position = this._cssPosition;
2445 styles.marginLeft = shouldBeFlushHorizontally ? '0' : this._leftOffset;
2446 styles.marginTop = shouldBeFlushVertically ? '0' : this._topOffset;
2447 styles.marginBottom = this._bottomOffset;
2448 styles.marginRight = this._rightOffset;
2449 if (shouldBeFlushHorizontally) {
2450 parentStyles.justifyContent = 'flex-start';
2451 }
2452 else if (this._justifyContent === 'center') {
2453 parentStyles.justifyContent = 'center';
2454 }
2455 else if (this._overlayRef.getConfig().direction === 'rtl') {
2456 // In RTL the browser will invert `flex-start` and `flex-end` automatically, but we
2457 // don't want that because our positioning is explicitly `left` and `right`, hence
2458 // why we do another inversion to ensure that the overlay stays in the same position.
2459 // TODO: reconsider this if we add `start` and `end` methods.
2460 if (this._justifyContent === 'flex-start') {
2461 parentStyles.justifyContent = 'flex-end';
2462 }
2463 else if (this._justifyContent === 'flex-end') {
2464 parentStyles.justifyContent = 'flex-start';
2465 }
2466 }
2467 else {
2468 parentStyles.justifyContent = this._justifyContent;
2469 }
2470 parentStyles.alignItems = shouldBeFlushVertically ? 'flex-start' : this._alignItems;
2471 }
2472 /**
2473 * Cleans up the DOM changes from the position strategy.
2474 * @docs-private
2475 */
2476 dispose() {
2477 if (this._isDisposed || !this._overlayRef) {
2478 return;
2479 }
2480 const styles = this._overlayRef.overlayElement.style;
2481 const parent = this._overlayRef.hostElement;
2482 const parentStyles = parent.style;
2483 parent.classList.remove(wrapperClass);
2484 parentStyles.justifyContent = parentStyles.alignItems = styles.marginTop =
2485 styles.marginBottom = styles.marginLeft = styles.marginRight = styles.position = '';
2486 this._overlayRef = null;
2487 this._isDisposed = true;
2488 }
2489}
2490
2491/**
2492 * @license
2493 * Copyright Google LLC All Rights Reserved.
2494 *
2495 * Use of this source code is governed by an MIT-style license that can be
2496 * found in the LICENSE file at https://angular.io/license
2497 */
2498/** Builder for overlay position strategy. */
2499class OverlayPositionBuilder {
2500 constructor(_viewportRuler, _document, _platform, _overlayContainer) {
2501 this._viewportRuler = _viewportRuler;
2502 this._document = _document;
2503 this._platform = _platform;
2504 this._overlayContainer = _overlayContainer;
2505 }
2506 /**
2507 * Creates a global position strategy.
2508 */
2509 global() {
2510 return new GlobalPositionStrategy();
2511 }
2512 /**
2513 * Creates a relative position strategy.
2514 * @param elementRef
2515 * @param originPos
2516 * @param overlayPos
2517 * @deprecated Use `flexibleConnectedTo` instead.
2518 * @breaking-change 8.0.0
2519 */
2520 connectedTo(elementRef, originPos, overlayPos) {
2521 return new ConnectedPositionStrategy(originPos, overlayPos, elementRef, this._viewportRuler, this._document, this._platform, this._overlayContainer);
2522 }
2523 /**
2524 * Creates a flexible position strategy.
2525 * @param origin Origin relative to which to position the overlay.
2526 */
2527 flexibleConnectedTo(origin) {
2528 return new FlexibleConnectedPositionStrategy(origin, this._viewportRuler, this._document, this._platform, this._overlayContainer);
2529 }
2530}
2531OverlayPositionBuilder.ɵfac = function OverlayPositionBuilder_Factory(t) { return new (t || OverlayPositionBuilder)(ɵngcc0.ɵɵinject(ɵngcc1.ViewportRuler), ɵngcc0.ɵɵinject(DOCUMENT), ɵngcc0.ɵɵinject(ɵngcc2.Platform), ɵngcc0.ɵɵinject(OverlayContainer)); };
2532OverlayPositionBuilder.ɵprov = i0.ɵɵdefineInjectable({ factory: function OverlayPositionBuilder_Factory() { return new OverlayPositionBuilder(i0.ɵɵinject(i1.ViewportRuler), i0.ɵɵinject(i1$1.DOCUMENT), i0.ɵɵinject(i2.Platform), i0.ɵɵinject(OverlayContainer)); }, token: OverlayPositionBuilder, providedIn: "root" });
2533OverlayPositionBuilder.ctorParameters = () => [
2534 { type: ViewportRuler },
2535 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
2536 { type: Platform },
2537 { type: OverlayContainer }
2538];
2539(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(OverlayPositionBuilder, [{
2540 type: Injectable,
2541 args: [{ providedIn: 'root' }]
2542 }], function () { return [{ type: ɵngcc1.ViewportRuler }, { type: undefined, decorators: [{
2543 type: Inject,
2544 args: [DOCUMENT]
2545 }] }, { type: ɵngcc2.Platform }, { type: OverlayContainer }]; }, null); })();
2546
2547/**
2548 * @license
2549 * Copyright Google LLC All Rights Reserved.
2550 *
2551 * Use of this source code is governed by an MIT-style license that can be
2552 * found in the LICENSE file at https://angular.io/license
2553 */
2554/** Next overlay unique ID. */
2555let nextUniqueId = 0;
2556// Note that Overlay is *not* scoped to the app root because of the ComponentFactoryResolver
2557// which needs to be different depending on where OverlayModule is imported.
2558/**
2559 * Service to create Overlays. Overlays are dynamically added pieces of floating UI, meant to be
2560 * used as a low-level building block for other components. Dialogs, tooltips, menus,
2561 * selects, etc. can all be built using overlays. The service should primarily be used by authors
2562 * of re-usable components rather than developers building end-user applications.
2563 *
2564 * An overlay *is* a PortalOutlet, so any kind of Portal can be loaded into one.
2565 */
2566class Overlay {
2567 constructor(
2568 /** Scrolling strategies that can be used when creating an overlay. */
2569 scrollStrategies, _overlayContainer, _componentFactoryResolver, _positionBuilder, _keyboardDispatcher, _injector, _ngZone, _document, _directionality, _location, _outsideClickDispatcher) {
2570 this.scrollStrategies = scrollStrategies;
2571 this._overlayContainer = _overlayContainer;
2572 this._componentFactoryResolver = _componentFactoryResolver;
2573 this._positionBuilder = _positionBuilder;
2574 this._keyboardDispatcher = _keyboardDispatcher;
2575 this._injector = _injector;
2576 this._ngZone = _ngZone;
2577 this._document = _document;
2578 this._directionality = _directionality;
2579 this._location = _location;
2580 this._outsideClickDispatcher = _outsideClickDispatcher;
2581 }
2582 /**
2583 * Creates an overlay.
2584 * @param config Configuration applied to the overlay.
2585 * @returns Reference to the created overlay.
2586 */
2587 create(config) {
2588 const host = this._createHostElement();
2589 const pane = this._createPaneElement(host);
2590 const portalOutlet = this._createPortalOutlet(pane);
2591 const overlayConfig = new OverlayConfig(config);
2592 overlayConfig.direction = overlayConfig.direction || this._directionality.value;
2593 return new OverlayRef(portalOutlet, host, pane, overlayConfig, this._ngZone, this._keyboardDispatcher, this._document, this._location, this._outsideClickDispatcher);
2594 }
2595 /**
2596 * Gets a position builder that can be used, via fluent API,
2597 * to construct and configure a position strategy.
2598 * @returns An overlay position builder.
2599 */
2600 position() {
2601 return this._positionBuilder;
2602 }
2603 /**
2604 * Creates the DOM element for an overlay and appends it to the overlay container.
2605 * @returns Newly-created pane element
2606 */
2607 _createPaneElement(host) {
2608 const pane = this._document.createElement('div');
2609 pane.id = `cdk-overlay-${nextUniqueId++}`;
2610 pane.classList.add('cdk-overlay-pane');
2611 host.appendChild(pane);
2612 return pane;
2613 }
2614 /**
2615 * Creates the host element that wraps around an overlay
2616 * and can be used for advanced positioning.
2617 * @returns Newly-create host element.
2618 */
2619 _createHostElement() {
2620 const host = this._document.createElement('div');
2621 this._overlayContainer.getContainerElement().appendChild(host);
2622 return host;
2623 }
2624 /**
2625 * Create a DomPortalOutlet into which the overlay content can be loaded.
2626 * @param pane The DOM element to turn into a portal outlet.
2627 * @returns A portal outlet for the given DOM element.
2628 */
2629 _createPortalOutlet(pane) {
2630 // We have to resolve the ApplicationRef later in order to allow people
2631 // to use overlay-based providers during app initialization.
2632 if (!this._appRef) {
2633 this._appRef = this._injector.get(ApplicationRef);
2634 }
2635 return new DomPortalOutlet(pane, this._componentFactoryResolver, this._appRef, this._injector, this._document);
2636 }
2637}
2638Overlay.ɵfac = function Overlay_Factory(t) { return new (t || Overlay)(ɵngcc0.ɵɵinject(ScrollStrategyOptions), ɵngcc0.ɵɵinject(OverlayContainer), ɵngcc0.ɵɵinject(ɵngcc0.ComponentFactoryResolver), ɵngcc0.ɵɵinject(OverlayPositionBuilder), ɵngcc0.ɵɵinject(OverlayKeyboardDispatcher), ɵngcc0.ɵɵinject(ɵngcc0.Injector), ɵngcc0.ɵɵinject(ɵngcc0.NgZone), ɵngcc0.ɵɵinject(DOCUMENT), ɵngcc0.ɵɵinject(ɵngcc3.Directionality), ɵngcc0.ɵɵinject(ɵngcc4.Location), ɵngcc0.ɵɵinject(OverlayOutsideClickDispatcher)); };
2639Overlay.ɵprov = /*@__PURE__*/ ɵngcc0.ɵɵdefineInjectable({ token: Overlay, factory: Overlay.ɵfac });
2640Overlay.ctorParameters = () => [
2641 { type: ScrollStrategyOptions },
2642 { type: OverlayContainer },
2643 { type: ComponentFactoryResolver },
2644 { type: OverlayPositionBuilder },
2645 { type: OverlayKeyboardDispatcher },
2646 { type: Injector },
2647 { type: NgZone },
2648 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
2649 { type: Directionality },
2650 { type: Location },
2651 { type: OverlayOutsideClickDispatcher }
2652];
2653(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(Overlay, [{
2654 type: Injectable
2655 }], function () { return [{ type: ScrollStrategyOptions }, { type: OverlayContainer }, { type: ɵngcc0.ComponentFactoryResolver }, { type: OverlayPositionBuilder }, { type: OverlayKeyboardDispatcher }, { type: ɵngcc0.Injector }, { type: ɵngcc0.NgZone }, { type: undefined, decorators: [{
2656 type: Inject,
2657 args: [DOCUMENT]
2658 }] }, { type: ɵngcc3.Directionality }, { type: ɵngcc4.Location }, { type: OverlayOutsideClickDispatcher }]; }, null); })();
2659
2660/**
2661 * @license
2662 * Copyright Google LLC All Rights Reserved.
2663 *
2664 * Use of this source code is governed by an MIT-style license that can be
2665 * found in the LICENSE file at https://angular.io/license
2666 */
2667/** Default set of positions for the overlay. Follows the behavior of a dropdown. */
2668const defaultPositionList = [
2669 {
2670 originX: 'start',
2671 originY: 'bottom',
2672 overlayX: 'start',
2673 overlayY: 'top'
2674 },
2675 {
2676 originX: 'start',
2677 originY: 'top',
2678 overlayX: 'start',
2679 overlayY: 'bottom'
2680 },
2681 {
2682 originX: 'end',
2683 originY: 'top',
2684 overlayX: 'end',
2685 overlayY: 'bottom'
2686 },
2687 {
2688 originX: 'end',
2689 originY: 'bottom',
2690 overlayX: 'end',
2691 overlayY: 'top'
2692 }
2693];
2694/** Injection token that determines the scroll handling while the connected overlay is open. */
2695const CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY = new InjectionToken('cdk-connected-overlay-scroll-strategy');
2696/**
2697 * Directive applied to an element to make it usable as an origin for an Overlay using a
2698 * ConnectedPositionStrategy.
2699 */
2700class CdkOverlayOrigin {
2701 constructor(
2702 /** Reference to the element on which the directive is applied. */
2703 elementRef) {
2704 this.elementRef = elementRef;
2705 }
2706}
2707CdkOverlayOrigin.ɵfac = function CdkOverlayOrigin_Factory(t) { return new (t || CdkOverlayOrigin)(ɵngcc0.ɵɵdirectiveInject(ɵngcc0.ElementRef)); };
2708CdkOverlayOrigin.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: CdkOverlayOrigin, selectors: [["", "cdk-overlay-origin", ""], ["", "overlay-origin", ""], ["", "cdkOverlayOrigin", ""]], exportAs: ["cdkOverlayOrigin"] });
2709CdkOverlayOrigin.ctorParameters = () => [
2710 { type: ElementRef }
2711];
2712(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(CdkOverlayOrigin, [{
2713 type: Directive,
2714 args: [{
2715 selector: '[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]',
2716 exportAs: 'cdkOverlayOrigin'
2717 }]
2718 }], function () { return [{ type: ɵngcc0.ElementRef }]; }, null); })();
2719/**
2720 * Directive to facilitate declarative creation of an
2721 * Overlay using a FlexibleConnectedPositionStrategy.
2722 */
2723class CdkConnectedOverlay {
2724 // TODO(jelbourn): inputs for size, scroll behavior, animation, etc.
2725 constructor(_overlay, templateRef, viewContainerRef, scrollStrategyFactory, _dir) {
2726 this._overlay = _overlay;
2727 this._dir = _dir;
2728 this._hasBackdrop = false;
2729 this._lockPosition = false;
2730 this._growAfterOpen = false;
2731 this._flexibleDimensions = false;
2732 this._push = false;
2733 this._backdropSubscription = Subscription.EMPTY;
2734 this._attachSubscription = Subscription.EMPTY;
2735 this._detachSubscription = Subscription.EMPTY;
2736 this._positionSubscription = Subscription.EMPTY;
2737 /** Margin between the overlay and the viewport edges. */
2738 this.viewportMargin = 0;
2739 /** Whether the overlay is open. */
2740 this.open = false;
2741 /** Whether the overlay can be closed by user interaction. */
2742 this.disableClose = false;
2743 /** Event emitted when the backdrop is clicked. */
2744 this.backdropClick = new EventEmitter();
2745 /** Event emitted when the position has changed. */
2746 this.positionChange = new EventEmitter();
2747 /** Event emitted when the overlay has been attached. */
2748 this.attach = new EventEmitter();
2749 /** Event emitted when the overlay has been detached. */
2750 this.detach = new EventEmitter();
2751 /** Emits when there are keyboard events that are targeted at the overlay. */
2752 this.overlayKeydown = new EventEmitter();
2753 /** Emits when there are mouse outside click events that are targeted at the overlay. */
2754 this.overlayOutsideClick = new EventEmitter();
2755 this._templatePortal = new TemplatePortal(templateRef, viewContainerRef);
2756 this._scrollStrategyFactory = scrollStrategyFactory;
2757 this.scrollStrategy = this._scrollStrategyFactory();
2758 }
2759 /** The offset in pixels for the overlay connection point on the x-axis */
2760 get offsetX() { return this._offsetX; }
2761 set offsetX(offsetX) {
2762 this._offsetX = offsetX;
2763 if (this._position) {
2764 this._updatePositionStrategy(this._position);
2765 }
2766 }
2767 /** The offset in pixels for the overlay connection point on the y-axis */
2768 get offsetY() { return this._offsetY; }
2769 set offsetY(offsetY) {
2770 this._offsetY = offsetY;
2771 if (this._position) {
2772 this._updatePositionStrategy(this._position);
2773 }
2774 }
2775 /** Whether or not the overlay should attach a backdrop. */
2776 get hasBackdrop() { return this._hasBackdrop; }
2777 set hasBackdrop(value) { this._hasBackdrop = coerceBooleanProperty(value); }
2778 /** Whether or not the overlay should be locked when scrolling. */
2779 get lockPosition() { return this._lockPosition; }
2780 set lockPosition(value) { this._lockPosition = coerceBooleanProperty(value); }
2781 /** Whether the overlay's width and height can be constrained to fit within the viewport. */
2782 get flexibleDimensions() { return this._flexibleDimensions; }
2783 set flexibleDimensions(value) {
2784 this._flexibleDimensions = coerceBooleanProperty(value);
2785 }
2786 /** Whether the overlay can grow after the initial open when flexible positioning is turned on. */
2787 get growAfterOpen() { return this._growAfterOpen; }
2788 set growAfterOpen(value) { this._growAfterOpen = coerceBooleanProperty(value); }
2789 /** Whether the overlay can be pushed on-screen if none of the provided positions fit. */
2790 get push() { return this._push; }
2791 set push(value) { this._push = coerceBooleanProperty(value); }
2792 /** The associated overlay reference. */
2793 get overlayRef() {
2794 return this._overlayRef;
2795 }
2796 /** The element's layout direction. */
2797 get dir() {
2798 return this._dir ? this._dir.value : 'ltr';
2799 }
2800 ngOnDestroy() {
2801 this._attachSubscription.unsubscribe();
2802 this._detachSubscription.unsubscribe();
2803 this._backdropSubscription.unsubscribe();
2804 this._positionSubscription.unsubscribe();
2805 if (this._overlayRef) {
2806 this._overlayRef.dispose();
2807 }
2808 }
2809 ngOnChanges(changes) {
2810 if (this._position) {
2811 this._updatePositionStrategy(this._position);
2812 this._overlayRef.updateSize({
2813 width: this.width,
2814 minWidth: this.minWidth,
2815 height: this.height,
2816 minHeight: this.minHeight,
2817 });
2818 if (changes['origin'] && this.open) {
2819 this._position.apply();
2820 }
2821 }
2822 if (changes['open']) {
2823 this.open ? this._attachOverlay() : this._detachOverlay();
2824 }
2825 }
2826 /** Creates an overlay */
2827 _createOverlay() {
2828 if (!this.positions || !this.positions.length) {
2829 this.positions = defaultPositionList;
2830 }
2831 const overlayRef = this._overlayRef = this._overlay.create(this._buildConfig());
2832 this._attachSubscription = overlayRef.attachments().subscribe(() => this.attach.emit());
2833 this._detachSubscription = overlayRef.detachments().subscribe(() => this.detach.emit());
2834 overlayRef.keydownEvents().subscribe((event) => {
2835 this.overlayKeydown.next(event);
2836 if (event.keyCode === ESCAPE && !this.disableClose && !hasModifierKey(event)) {
2837 event.preventDefault();
2838 this._detachOverlay();
2839 }
2840 });
2841 this._overlayRef.outsidePointerEvents().subscribe((event) => {
2842 this.overlayOutsideClick.next(event);
2843 });
2844 }
2845 /** Builds the overlay config based on the directive's inputs */
2846 _buildConfig() {
2847 const positionStrategy = this._position =
2848 this.positionStrategy || this._createPositionStrategy();
2849 const overlayConfig = new OverlayConfig({
2850 direction: this._dir,
2851 positionStrategy,
2852 scrollStrategy: this.scrollStrategy,
2853 hasBackdrop: this.hasBackdrop
2854 });
2855 if (this.width || this.width === 0) {
2856 overlayConfig.width = this.width;
2857 }
2858 if (this.height || this.height === 0) {
2859 overlayConfig.height = this.height;
2860 }
2861 if (this.minWidth || this.minWidth === 0) {
2862 overlayConfig.minWidth = this.minWidth;
2863 }
2864 if (this.minHeight || this.minHeight === 0) {
2865 overlayConfig.minHeight = this.minHeight;
2866 }
2867 if (this.backdropClass) {
2868 overlayConfig.backdropClass = this.backdropClass;
2869 }
2870 if (this.panelClass) {
2871 overlayConfig.panelClass = this.panelClass;
2872 }
2873 return overlayConfig;
2874 }
2875 /** Updates the state of a position strategy, based on the values of the directive inputs. */
2876 _updatePositionStrategy(positionStrategy) {
2877 const positions = this.positions.map(currentPosition => ({
2878 originX: currentPosition.originX,
2879 originY: currentPosition.originY,
2880 overlayX: currentPosition.overlayX,
2881 overlayY: currentPosition.overlayY,
2882 offsetX: currentPosition.offsetX || this.offsetX,
2883 offsetY: currentPosition.offsetY || this.offsetY,
2884 panelClass: currentPosition.panelClass || undefined,
2885 }));
2886 return positionStrategy
2887 .setOrigin(this.origin.elementRef)
2888 .withPositions(positions)
2889 .withFlexibleDimensions(this.flexibleDimensions)
2890 .withPush(this.push)
2891 .withGrowAfterOpen(this.growAfterOpen)
2892 .withViewportMargin(this.viewportMargin)
2893 .withLockedPosition(this.lockPosition)
2894 .withTransformOriginOn(this.transformOriginSelector);
2895 }
2896 /** Returns the position strategy of the overlay to be set on the overlay config */
2897 _createPositionStrategy() {
2898 const strategy = this._overlay.position().flexibleConnectedTo(this.origin.elementRef);
2899 this._updatePositionStrategy(strategy);
2900 return strategy;
2901 }
2902 /** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */
2903 _attachOverlay() {
2904 if (!this._overlayRef) {
2905 this._createOverlay();
2906 }
2907 else {
2908 // Update the overlay size, in case the directive's inputs have changed
2909 this._overlayRef.getConfig().hasBackdrop = this.hasBackdrop;
2910 }
2911 if (!this._overlayRef.hasAttached()) {
2912 this._overlayRef.attach(this._templatePortal);
2913 }
2914 if (this.hasBackdrop) {
2915 this._backdropSubscription = this._overlayRef.backdropClick().subscribe(event => {
2916 this.backdropClick.emit(event);
2917 });
2918 }
2919 else {
2920 this._backdropSubscription.unsubscribe();
2921 }
2922 this._positionSubscription.unsubscribe();
2923 // Only subscribe to `positionChanges` if requested, because putting
2924 // together all the information for it can be expensive.
2925 if (this.positionChange.observers.length > 0) {
2926 this._positionSubscription = this._position.positionChanges
2927 .pipe(takeWhile(() => this.positionChange.observers.length > 0))
2928 .subscribe(position => {
2929 this.positionChange.emit(position);
2930 if (this.positionChange.observers.length === 0) {
2931 this._positionSubscription.unsubscribe();
2932 }
2933 });
2934 }
2935 }
2936 /** Detaches the overlay and unsubscribes to backdrop clicks if backdrop exists */
2937 _detachOverlay() {
2938 if (this._overlayRef) {
2939 this._overlayRef.detach();
2940 }
2941 this._backdropSubscription.unsubscribe();
2942 this._positionSubscription.unsubscribe();
2943 }
2944}
2945CdkConnectedOverlay.ɵfac = function CdkConnectedOverlay_Factory(t) { return new (t || CdkConnectedOverlay)(ɵngcc0.ɵɵdirectiveInject(Overlay), ɵngcc0.ɵɵdirectiveInject(ɵngcc0.TemplateRef), ɵngcc0.ɵɵdirectiveInject(ɵngcc0.ViewContainerRef), ɵngcc0.ɵɵdirectiveInject(CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY), ɵngcc0.ɵɵdirectiveInject(ɵngcc3.Directionality, 8)); };
2946CdkConnectedOverlay.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: CdkConnectedOverlay, selectors: [["", "cdk-connected-overlay", ""], ["", "connected-overlay", ""], ["", "cdkConnectedOverlay", ""]], inputs: { viewportMargin: ["cdkConnectedOverlayViewportMargin", "viewportMargin"], open: ["cdkConnectedOverlayOpen", "open"], disableClose: ["cdkConnectedOverlayDisableClose", "disableClose"], scrollStrategy: ["cdkConnectedOverlayScrollStrategy", "scrollStrategy"], offsetX: ["cdkConnectedOverlayOffsetX", "offsetX"], offsetY: ["cdkConnectedOverlayOffsetY", "offsetY"], hasBackdrop: ["cdkConnectedOverlayHasBackdrop", "hasBackdrop"], lockPosition: ["cdkConnectedOverlayLockPosition", "lockPosition"], flexibleDimensions: ["cdkConnectedOverlayFlexibleDimensions", "flexibleDimensions"], growAfterOpen: ["cdkConnectedOverlayGrowAfterOpen", "growAfterOpen"], push: ["cdkConnectedOverlayPush", "push"], positions: ["cdkConnectedOverlayPositions", "positions"], origin: ["cdkConnectedOverlayOrigin", "origin"], positionStrategy: ["cdkConnectedOverlayPositionStrategy", "positionStrategy"], width: ["cdkConnectedOverlayWidth", "width"], height: ["cdkConnectedOverlayHeight", "height"], minWidth: ["cdkConnectedOverlayMinWidth", "minWidth"], minHeight: ["cdkConnectedOverlayMinHeight", "minHeight"], backdropClass: ["cdkConnectedOverlayBackdropClass", "backdropClass"], panelClass: ["cdkConnectedOverlayPanelClass", "panelClass"], transformOriginSelector: ["cdkConnectedOverlayTransformOriginOn", "transformOriginSelector"] }, outputs: { backdropClick: "backdropClick", positionChange: "positionChange", attach: "attach", detach: "detach", overlayKeydown: "overlayKeydown", overlayOutsideClick: "overlayOutsideClick" }, exportAs: ["cdkConnectedOverlay"], features: [ɵngcc0.ɵɵNgOnChangesFeature] });
2947CdkConnectedOverlay.ctorParameters = () => [
2948 { type: Overlay },
2949 { type: TemplateRef },
2950 { type: ViewContainerRef },
2951 { type: undefined, decorators: [{ type: Inject, args: [CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY,] }] },
2952 { type: Directionality, decorators: [{ type: Optional }] }
2953];
2954CdkConnectedOverlay.propDecorators = {
2955 origin: [{ type: Input, args: ['cdkConnectedOverlayOrigin',] }],
2956 positions: [{ type: Input, args: ['cdkConnectedOverlayPositions',] }],
2957 positionStrategy: [{ type: Input, args: ['cdkConnectedOverlayPositionStrategy',] }],
2958 offsetX: [{ type: Input, args: ['cdkConnectedOverlayOffsetX',] }],
2959 offsetY: [{ type: Input, args: ['cdkConnectedOverlayOffsetY',] }],
2960 width: [{ type: Input, args: ['cdkConnectedOverlayWidth',] }],
2961 height: [{ type: Input, args: ['cdkConnectedOverlayHeight',] }],
2962 minWidth: [{ type: Input, args: ['cdkConnectedOverlayMinWidth',] }],
2963 minHeight: [{ type: Input, args: ['cdkConnectedOverlayMinHeight',] }],
2964 backdropClass: [{ type: Input, args: ['cdkConnectedOverlayBackdropClass',] }],
2965 panelClass: [{ type: Input, args: ['cdkConnectedOverlayPanelClass',] }],
2966 viewportMargin: [{ type: Input, args: ['cdkConnectedOverlayViewportMargin',] }],
2967 scrollStrategy: [{ type: Input, args: ['cdkConnectedOverlayScrollStrategy',] }],
2968 open: [{ type: Input, args: ['cdkConnectedOverlayOpen',] }],
2969 disableClose: [{ type: Input, args: ['cdkConnectedOverlayDisableClose',] }],
2970 transformOriginSelector: [{ type: Input, args: ['cdkConnectedOverlayTransformOriginOn',] }],
2971 hasBackdrop: [{ type: Input, args: ['cdkConnectedOverlayHasBackdrop',] }],
2972 lockPosition: [{ type: Input, args: ['cdkConnectedOverlayLockPosition',] }],
2973 flexibleDimensions: [{ type: Input, args: ['cdkConnectedOverlayFlexibleDimensions',] }],
2974 growAfterOpen: [{ type: Input, args: ['cdkConnectedOverlayGrowAfterOpen',] }],
2975 push: [{ type: Input, args: ['cdkConnectedOverlayPush',] }],
2976 backdropClick: [{ type: Output }],
2977 positionChange: [{ type: Output }],
2978 attach: [{ type: Output }],
2979 detach: [{ type: Output }],
2980 overlayKeydown: [{ type: Output }],
2981 overlayOutsideClick: [{ type: Output }]
2982};
2983(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(CdkConnectedOverlay, [{
2984 type: Directive,
2985 args: [{
2986 selector: '[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]',
2987 exportAs: 'cdkConnectedOverlay'
2988 }]
2989 }], function () { return [{ type: Overlay }, { type: ɵngcc0.TemplateRef }, { type: ɵngcc0.ViewContainerRef }, { type: undefined, decorators: [{
2990 type: Inject,
2991 args: [CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY]
2992 }] }, { type: ɵngcc3.Directionality, decorators: [{
2993 type: Optional
2994 }] }]; }, { viewportMargin: [{
2995 type: Input,
2996 args: ['cdkConnectedOverlayViewportMargin']
2997 }], open: [{
2998 type: Input,
2999 args: ['cdkConnectedOverlayOpen']
3000 }], disableClose: [{
3001 type: Input,
3002 args: ['cdkConnectedOverlayDisableClose']
3003 }], backdropClick: [{
3004 type: Output
3005 }], positionChange: [{
3006 type: Output
3007 }], attach: [{
3008 type: Output
3009 }], detach: [{
3010 type: Output
3011 }], overlayKeydown: [{
3012 type: Output
3013 }], overlayOutsideClick: [{
3014 type: Output
3015 }], scrollStrategy: [{
3016 type: Input,
3017 args: ['cdkConnectedOverlayScrollStrategy']
3018 }], offsetX: [{
3019 type: Input,
3020 args: ['cdkConnectedOverlayOffsetX']
3021 }], offsetY: [{
3022 type: Input,
3023 args: ['cdkConnectedOverlayOffsetY']
3024 }], hasBackdrop: [{
3025 type: Input,
3026 args: ['cdkConnectedOverlayHasBackdrop']
3027 }], lockPosition: [{
3028 type: Input,
3029 args: ['cdkConnectedOverlayLockPosition']
3030 }], flexibleDimensions: [{
3031 type: Input,
3032 args: ['cdkConnectedOverlayFlexibleDimensions']
3033 }], growAfterOpen: [{
3034 type: Input,
3035 args: ['cdkConnectedOverlayGrowAfterOpen']
3036 }], push: [{
3037 type: Input,
3038 args: ['cdkConnectedOverlayPush']
3039 }], positions: [{
3040 type: Input,
3041 args: ['cdkConnectedOverlayPositions']
3042 }], origin: [{
3043 type: Input,
3044 args: ['cdkConnectedOverlayOrigin']
3045 }], positionStrategy: [{
3046 type: Input,
3047 args: ['cdkConnectedOverlayPositionStrategy']
3048 }], width: [{
3049 type: Input,
3050 args: ['cdkConnectedOverlayWidth']
3051 }], height: [{
3052 type: Input,
3053 args: ['cdkConnectedOverlayHeight']
3054 }], minWidth: [{
3055 type: Input,
3056 args: ['cdkConnectedOverlayMinWidth']
3057 }], minHeight: [{
3058 type: Input,
3059 args: ['cdkConnectedOverlayMinHeight']
3060 }], backdropClass: [{
3061 type: Input,
3062 args: ['cdkConnectedOverlayBackdropClass']
3063 }], panelClass: [{
3064 type: Input,
3065 args: ['cdkConnectedOverlayPanelClass']
3066 }], transformOriginSelector: [{
3067 type: Input,
3068 args: ['cdkConnectedOverlayTransformOriginOn']
3069 }] }); })();
3070/** @docs-private */
3071function CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay) {
3072 return () => overlay.scrollStrategies.reposition();
3073}
3074/** @docs-private */
3075const CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER = {
3076 provide: CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY,
3077 deps: [Overlay],
3078 useFactory: CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY,
3079};
3080
3081/**
3082 * @license
3083 * Copyright Google LLC All Rights Reserved.
3084 *
3085 * Use of this source code is governed by an MIT-style license that can be
3086 * found in the LICENSE file at https://angular.io/license
3087 */
3088class OverlayModule {
3089}
3090OverlayModule.ɵfac = function OverlayModule_Factory(t) { return new (t || OverlayModule)(); };
3091OverlayModule.ɵmod = /*@__PURE__*/ ɵngcc0.ɵɵdefineNgModule({ type: OverlayModule });
3092OverlayModule.ɵinj = /*@__PURE__*/ ɵngcc0.ɵɵdefineInjector({ providers: [
3093 Overlay,
3094 CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER,
3095 ], imports: [[BidiModule, PortalModule, ScrollingModule], ScrollingModule] });
3096(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(OverlayModule, [{
3097 type: NgModule,
3098 args: [{
3099 imports: [BidiModule, PortalModule, ScrollingModule],
3100 exports: [CdkConnectedOverlay, CdkOverlayOrigin, ScrollingModule],
3101 declarations: [CdkConnectedOverlay, CdkOverlayOrigin],
3102 providers: [
3103 Overlay,
3104 CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER,
3105 ]
3106 }]
3107 }], null, null); })();
3108(function () { (typeof ngJitMode === "undefined" || ngJitMode) && ɵngcc0.ɵɵsetNgModuleScope(OverlayModule, { declarations: function () { return [CdkConnectedOverlay, CdkOverlayOrigin]; }, imports: function () { return [BidiModule, PortalModule, ScrollingModule]; }, exports: function () { return [CdkConnectedOverlay, CdkOverlayOrigin, ScrollingModule]; } }); })();
3109
3110/**
3111 * @license
3112 * Copyright Google LLC All Rights Reserved.
3113 *
3114 * Use of this source code is governed by an MIT-style license that can be
3115 * found in the LICENSE file at https://angular.io/license
3116 */
3117
3118/**
3119 * @license
3120 * Copyright Google LLC All Rights Reserved.
3121 *
3122 * Use of this source code is governed by an MIT-style license that can be
3123 * found in the LICENSE file at https://angular.io/license
3124 */
3125/**
3126 * Alternative to OverlayContainer that supports correct displaying of overlay elements in
3127 * Fullscreen mode
3128 * https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen
3129 *
3130 * Should be provided in the root component.
3131 */
3132class FullscreenOverlayContainer extends OverlayContainer {
3133 constructor(_document, platform) {
3134 super(_document, platform);
3135 }
3136 ngOnDestroy() {
3137 super.ngOnDestroy();
3138 if (this._fullScreenEventName && this._fullScreenListener) {
3139 this._document.removeEventListener(this._fullScreenEventName, this._fullScreenListener);
3140 }
3141 }
3142 _createContainer() {
3143 super._createContainer();
3144 this._adjustParentForFullscreenChange();
3145 this._addFullscreenChangeListener(() => this._adjustParentForFullscreenChange());
3146 }
3147 _adjustParentForFullscreenChange() {
3148 if (!this._containerElement) {
3149 return;
3150 }
3151 const fullscreenElement = this.getFullscreenElement();
3152 const parent = fullscreenElement || this._document.body;
3153 parent.appendChild(this._containerElement);
3154 }
3155 _addFullscreenChangeListener(fn) {
3156 const eventName = this._getEventName();
3157 if (eventName) {
3158 if (this._fullScreenListener) {
3159 this._document.removeEventListener(eventName, this._fullScreenListener);
3160 }
3161 this._document.addEventListener(eventName, fn);
3162 this._fullScreenListener = fn;
3163 }
3164 }
3165 _getEventName() {
3166 if (!this._fullScreenEventName) {
3167 const _document = this._document;
3168 if (_document.fullscreenEnabled) {
3169 this._fullScreenEventName = 'fullscreenchange';
3170 }
3171 else if (_document.webkitFullscreenEnabled) {
3172 this._fullScreenEventName = 'webkitfullscreenchange';
3173 }
3174 else if (_document.mozFullScreenEnabled) {
3175 this._fullScreenEventName = 'mozfullscreenchange';
3176 }
3177 else if (_document.msFullscreenEnabled) {
3178 this._fullScreenEventName = 'MSFullscreenChange';
3179 }
3180 }
3181 return this._fullScreenEventName;
3182 }
3183 /**
3184 * When the page is put into fullscreen mode, a specific element is specified.
3185 * Only that element and its children are visible when in fullscreen mode.
3186 */
3187 getFullscreenElement() {
3188 const _document = this._document;
3189 return _document.fullscreenElement ||
3190 _document.webkitFullscreenElement ||
3191 _document.mozFullScreenElement ||
3192 _document.msFullscreenElement ||
3193 null;
3194 }
3195}
3196FullscreenOverlayContainer.ɵfac = function FullscreenOverlayContainer_Factory(t) { return new (t || FullscreenOverlayContainer)(ɵngcc0.ɵɵinject(DOCUMENT), ɵngcc0.ɵɵinject(ɵngcc2.Platform)); };
3197FullscreenOverlayContainer.ɵprov = i0.ɵɵdefineInjectable({ factory: function FullscreenOverlayContainer_Factory() { return new FullscreenOverlayContainer(i0.ɵɵinject(i1$1.DOCUMENT), i0.ɵɵinject(i2.Platform)); }, token: FullscreenOverlayContainer, providedIn: "root" });
3198FullscreenOverlayContainer.ctorParameters = () => [
3199 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
3200 { type: Platform }
3201];
3202(function () { (typeof ngDevMode === "undefined" || ngDevMode) && ɵngcc0.ɵsetClassMetadata(FullscreenOverlayContainer, [{
3203 type: Injectable,
3204 args: [{ providedIn: 'root' }]
3205 }], function () { return [{ type: undefined, decorators: [{
3206 type: Inject,
3207 args: [DOCUMENT]
3208 }] }, { type: ɵngcc2.Platform }]; }, null); })();
3209
3210/**
3211 * @license
3212 * Copyright Google LLC All Rights Reserved.
3213 *
3214 * Use of this source code is governed by an MIT-style license that can be
3215 * found in the LICENSE file at https://angular.io/license
3216 */
3217
3218/**
3219 * Generated bundle index. Do not edit.
3220 */
3221
3222export { BlockScrollStrategy, CdkConnectedOverlay, CdkOverlayOrigin, CloseScrollStrategy, ConnectedOverlayPositionChange, ConnectedPositionStrategy, ConnectionPositionPair, FlexibleConnectedPositionStrategy, FullscreenOverlayContainer, GlobalPositionStrategy, NoopScrollStrategy, Overlay, OverlayConfig, OverlayContainer, OverlayKeyboardDispatcher, OverlayModule, OverlayOutsideClickDispatcher, OverlayPositionBuilder, OverlayRef, RepositionScrollStrategy, ScrollStrategyOptions, ScrollingVisibility, validateHorizontalPosition, validateVerticalPosition, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY as ɵangular_material_src_cdk_overlay_overlay_a, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY as ɵangular_material_src_cdk_overlay_overlay_b, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER as ɵangular_material_src_cdk_overlay_overlay_c, BaseOverlayDispatcher as ɵangular_material_src_cdk_overlay_overlay_d };
3223
3224//# sourceMappingURL=overlay.js.map
Note: See TracBrowser for help on using the repository browser.