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

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

primeNG components

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