UNPKG

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