You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4992 lines
173 KiB
4992 lines
173 KiB
import {
|
|
ESCAPE,
|
|
hasModifierKey
|
|
} from "./chunk-JGP5BR6B.js";
|
|
import {
|
|
BidiModule,
|
|
Directionality
|
|
} from "./chunk-6NZ2BEBG.js";
|
|
import {
|
|
Platform,
|
|
_getEventTarget,
|
|
_isTestEnvironment,
|
|
getRtlScrollAxisType,
|
|
supportsScrollBehavior
|
|
} from "./chunk-IAC53GOD.js";
|
|
import {
|
|
coerceArray,
|
|
coerceCssPixelValue,
|
|
coerceElement,
|
|
coerceNumberProperty
|
|
} from "./chunk-M5GY4FCO.js";
|
|
import {
|
|
DOCUMENT,
|
|
Location
|
|
} from "./chunk-OSQBUYO6.js";
|
|
import {
|
|
ANIMATION_MODULE_TYPE,
|
|
ApplicationRef,
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
ComponentFactoryResolver$1,
|
|
ConnectableObservable,
|
|
Directive,
|
|
ElementRef,
|
|
EventEmitter,
|
|
Inject,
|
|
Injectable,
|
|
InjectionToken,
|
|
Injector,
|
|
Input,
|
|
IterableDiffers,
|
|
NgModule,
|
|
NgZone,
|
|
Observable,
|
|
Optional,
|
|
Output,
|
|
SkipSelf,
|
|
Subject,
|
|
Subscription,
|
|
TemplateRef,
|
|
ViewChild,
|
|
ViewContainerRef,
|
|
ViewEncapsulation$1,
|
|
animationFrameScheduler,
|
|
asapScheduler,
|
|
auditTime,
|
|
booleanAttribute,
|
|
distinctUntilChanged,
|
|
filter,
|
|
forwardRef,
|
|
fromEvent,
|
|
inject,
|
|
isObservable,
|
|
merge,
|
|
of,
|
|
pairwise,
|
|
setClassMetadata,
|
|
shareReplay,
|
|
startWith,
|
|
switchMap,
|
|
take,
|
|
takeUntil,
|
|
takeWhile,
|
|
ɵɵInheritDefinitionFeature,
|
|
ɵɵInputTransformsFeature,
|
|
ɵɵNgOnChangesFeature,
|
|
ɵɵProvidersFeature,
|
|
ɵɵStandaloneFeature,
|
|
ɵɵadvance,
|
|
ɵɵclassProp,
|
|
ɵɵdefineComponent,
|
|
ɵɵdefineDirective,
|
|
ɵɵdefineInjectable,
|
|
ɵɵdefineInjector,
|
|
ɵɵdefineNgModule,
|
|
ɵɵdirectiveInject,
|
|
ɵɵelement,
|
|
ɵɵelementEnd,
|
|
ɵɵelementStart,
|
|
ɵɵgetInheritedFactory,
|
|
ɵɵinject,
|
|
ɵɵloadQuery,
|
|
ɵɵprojection,
|
|
ɵɵprojectionDef,
|
|
ɵɵqueryRefresh,
|
|
ɵɵstyleProp,
|
|
ɵɵviewQuery
|
|
} from "./chunk-FGESKT7O.js";
|
|
import {
|
|
__spreadProps,
|
|
__spreadValues
|
|
} from "./chunk-HSNDBVJ3.js";
|
|
|
|
// node_modules/@angular/cdk/fesm2022/collections.mjs
|
|
var DataSource = class {
|
|
};
|
|
function isDataSource(value) {
|
|
return value && typeof value.connect === "function" && !(value instanceof ConnectableObservable);
|
|
}
|
|
var ArrayDataSource = class extends DataSource {
|
|
constructor(_data) {
|
|
super();
|
|
this._data = _data;
|
|
}
|
|
connect() {
|
|
return isObservable(this._data) ? this._data : of(this._data);
|
|
}
|
|
disconnect() {
|
|
}
|
|
};
|
|
var _RecycleViewRepeaterStrategy = class {
|
|
constructor() {
|
|
this.viewCacheSize = 20;
|
|
this._viewCache = [];
|
|
}
|
|
/** Apply changes to the DOM. */
|
|
applyChanges(changes, viewContainerRef, itemContextFactory, itemValueResolver, itemViewChanged) {
|
|
changes.forEachOperation((record, adjustedPreviousIndex, currentIndex) => {
|
|
let view;
|
|
let operation;
|
|
if (record.previousIndex == null) {
|
|
const viewArgsFactory = () => itemContextFactory(record, adjustedPreviousIndex, currentIndex);
|
|
view = this._insertView(viewArgsFactory, currentIndex, viewContainerRef, itemValueResolver(record));
|
|
operation = view ? 1 : 0;
|
|
} else if (currentIndex == null) {
|
|
this._detachAndCacheView(adjustedPreviousIndex, viewContainerRef);
|
|
operation = 3;
|
|
} else {
|
|
view = this._moveView(adjustedPreviousIndex, currentIndex, viewContainerRef, itemValueResolver(record));
|
|
operation = 2;
|
|
}
|
|
if (itemViewChanged) {
|
|
itemViewChanged({
|
|
context: view?.context,
|
|
operation,
|
|
record
|
|
});
|
|
}
|
|
});
|
|
}
|
|
detach() {
|
|
for (const view of this._viewCache) {
|
|
view.destroy();
|
|
}
|
|
this._viewCache = [];
|
|
}
|
|
/**
|
|
* Inserts a view for a new item, either from the cache or by creating a new
|
|
* one. Returns `undefined` if the item was inserted into a cached view.
|
|
*/
|
|
_insertView(viewArgsFactory, currentIndex, viewContainerRef, value) {
|
|
const cachedView = this._insertViewFromCache(currentIndex, viewContainerRef);
|
|
if (cachedView) {
|
|
cachedView.context.$implicit = value;
|
|
return void 0;
|
|
}
|
|
const viewArgs = viewArgsFactory();
|
|
return viewContainerRef.createEmbeddedView(viewArgs.templateRef, viewArgs.context, viewArgs.index);
|
|
}
|
|
/** Detaches the view at the given index and inserts into the view cache. */
|
|
_detachAndCacheView(index, viewContainerRef) {
|
|
const detachedView = viewContainerRef.detach(index);
|
|
this._maybeCacheView(detachedView, viewContainerRef);
|
|
}
|
|
/** Moves view at the previous index to the current index. */
|
|
_moveView(adjustedPreviousIndex, currentIndex, viewContainerRef, value) {
|
|
const view = viewContainerRef.get(adjustedPreviousIndex);
|
|
viewContainerRef.move(view, currentIndex);
|
|
view.context.$implicit = value;
|
|
return view;
|
|
}
|
|
/**
|
|
* Cache the given detached view. If the cache is full, the view will be
|
|
* destroyed.
|
|
*/
|
|
_maybeCacheView(view, viewContainerRef) {
|
|
if (this._viewCache.length < this.viewCacheSize) {
|
|
this._viewCache.push(view);
|
|
} else {
|
|
const index = viewContainerRef.indexOf(view);
|
|
if (index === -1) {
|
|
view.destroy();
|
|
} else {
|
|
viewContainerRef.remove(index);
|
|
}
|
|
}
|
|
}
|
|
/** Inserts a recycled view from the cache at the given index. */
|
|
_insertViewFromCache(index, viewContainerRef) {
|
|
const cachedView = this._viewCache.pop();
|
|
if (cachedView) {
|
|
viewContainerRef.insert(cachedView, index);
|
|
}
|
|
return cachedView || null;
|
|
}
|
|
};
|
|
var _UniqueSelectionDispatcher = class _UniqueSelectionDispatcher {
|
|
constructor() {
|
|
this._listeners = [];
|
|
}
|
|
/**
|
|
* Notify other items that selection for the given name has been set.
|
|
* @param id ID of the item.
|
|
* @param name Name of the item.
|
|
*/
|
|
notify(id, name) {
|
|
for (let listener of this._listeners) {
|
|
listener(id, name);
|
|
}
|
|
}
|
|
/**
|
|
* Listen for future changes to item selection.
|
|
* @return Function used to deregister listener
|
|
*/
|
|
listen(listener) {
|
|
this._listeners.push(listener);
|
|
return () => {
|
|
this._listeners = this._listeners.filter((registered) => {
|
|
return listener !== registered;
|
|
});
|
|
};
|
|
}
|
|
ngOnDestroy() {
|
|
this._listeners = [];
|
|
}
|
|
};
|
|
_UniqueSelectionDispatcher.ɵfac = function UniqueSelectionDispatcher_Factory(t) {
|
|
return new (t || _UniqueSelectionDispatcher)();
|
|
};
|
|
_UniqueSelectionDispatcher.ɵprov = ɵɵdefineInjectable({
|
|
token: _UniqueSelectionDispatcher,
|
|
factory: _UniqueSelectionDispatcher.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var UniqueSelectionDispatcher = _UniqueSelectionDispatcher;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(UniqueSelectionDispatcher, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var _VIEW_REPEATER_STRATEGY = new InjectionToken("_ViewRepeater");
|
|
|
|
// node_modules/@angular/cdk/fesm2022/scrolling.mjs
|
|
var _c0 = ["contentWrapper"];
|
|
var _c1 = ["*"];
|
|
var VIRTUAL_SCROLL_STRATEGY = new InjectionToken("VIRTUAL_SCROLL_STRATEGY");
|
|
var FixedSizeVirtualScrollStrategy = class {
|
|
/**
|
|
* @param itemSize The size of the items in the virtually scrolling list.
|
|
* @param minBufferPx The minimum amount of buffer (in pixels) before needing to render more
|
|
* @param maxBufferPx The amount of buffer (in pixels) to render when rendering more.
|
|
*/
|
|
constructor(itemSize, minBufferPx, maxBufferPx) {
|
|
this._scrolledIndexChange = new Subject();
|
|
this.scrolledIndexChange = this._scrolledIndexChange.pipe(distinctUntilChanged());
|
|
this._viewport = null;
|
|
this._itemSize = itemSize;
|
|
this._minBufferPx = minBufferPx;
|
|
this._maxBufferPx = maxBufferPx;
|
|
}
|
|
/**
|
|
* Attaches this scroll strategy to a viewport.
|
|
* @param viewport The viewport to attach this strategy to.
|
|
*/
|
|
attach(viewport) {
|
|
this._viewport = viewport;
|
|
this._updateTotalContentSize();
|
|
this._updateRenderedRange();
|
|
}
|
|
/** Detaches this scroll strategy from the currently attached viewport. */
|
|
detach() {
|
|
this._scrolledIndexChange.complete();
|
|
this._viewport = null;
|
|
}
|
|
/**
|
|
* Update the item size and buffer size.
|
|
* @param itemSize The size of the items in the virtually scrolling list.
|
|
* @param minBufferPx The minimum amount of buffer (in pixels) before needing to render more
|
|
* @param maxBufferPx The amount of buffer (in pixels) to render when rendering more.
|
|
*/
|
|
updateItemAndBufferSize(itemSize, minBufferPx, maxBufferPx) {
|
|
if (maxBufferPx < minBufferPx && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error("CDK virtual scroll: maxBufferPx must be greater than or equal to minBufferPx");
|
|
}
|
|
this._itemSize = itemSize;
|
|
this._minBufferPx = minBufferPx;
|
|
this._maxBufferPx = maxBufferPx;
|
|
this._updateTotalContentSize();
|
|
this._updateRenderedRange();
|
|
}
|
|
/** @docs-private Implemented as part of VirtualScrollStrategy. */
|
|
onContentScrolled() {
|
|
this._updateRenderedRange();
|
|
}
|
|
/** @docs-private Implemented as part of VirtualScrollStrategy. */
|
|
onDataLengthChanged() {
|
|
this._updateTotalContentSize();
|
|
this._updateRenderedRange();
|
|
}
|
|
/** @docs-private Implemented as part of VirtualScrollStrategy. */
|
|
onContentRendered() {
|
|
}
|
|
/** @docs-private Implemented as part of VirtualScrollStrategy. */
|
|
onRenderedOffsetChanged() {
|
|
}
|
|
/**
|
|
* Scroll to the offset for the given index.
|
|
* @param index The index of the element to scroll to.
|
|
* @param behavior The ScrollBehavior to use when scrolling.
|
|
*/
|
|
scrollToIndex(index, behavior) {
|
|
if (this._viewport) {
|
|
this._viewport.scrollToOffset(index * this._itemSize, behavior);
|
|
}
|
|
}
|
|
/** Update the viewport's total content size. */
|
|
_updateTotalContentSize() {
|
|
if (!this._viewport) {
|
|
return;
|
|
}
|
|
this._viewport.setTotalContentSize(this._viewport.getDataLength() * this._itemSize);
|
|
}
|
|
/** Update the viewport's rendered range. */
|
|
_updateRenderedRange() {
|
|
if (!this._viewport) {
|
|
return;
|
|
}
|
|
const renderedRange = this._viewport.getRenderedRange();
|
|
const newRange = {
|
|
start: renderedRange.start,
|
|
end: renderedRange.end
|
|
};
|
|
const viewportSize = this._viewport.getViewportSize();
|
|
const dataLength = this._viewport.getDataLength();
|
|
let scrollOffset = this._viewport.measureScrollOffset();
|
|
let firstVisibleIndex = this._itemSize > 0 ? scrollOffset / this._itemSize : 0;
|
|
if (newRange.end > dataLength) {
|
|
const maxVisibleItems = Math.ceil(viewportSize / this._itemSize);
|
|
const newVisibleIndex = Math.max(0, Math.min(firstVisibleIndex, dataLength - maxVisibleItems));
|
|
if (firstVisibleIndex != newVisibleIndex) {
|
|
firstVisibleIndex = newVisibleIndex;
|
|
scrollOffset = newVisibleIndex * this._itemSize;
|
|
newRange.start = Math.floor(firstVisibleIndex);
|
|
}
|
|
newRange.end = Math.max(0, Math.min(dataLength, newRange.start + maxVisibleItems));
|
|
}
|
|
const startBuffer = scrollOffset - newRange.start * this._itemSize;
|
|
if (startBuffer < this._minBufferPx && newRange.start != 0) {
|
|
const expandStart = Math.ceil((this._maxBufferPx - startBuffer) / this._itemSize);
|
|
newRange.start = Math.max(0, newRange.start - expandStart);
|
|
newRange.end = Math.min(dataLength, Math.ceil(firstVisibleIndex + (viewportSize + this._minBufferPx) / this._itemSize));
|
|
} else {
|
|
const endBuffer = newRange.end * this._itemSize - (scrollOffset + viewportSize);
|
|
if (endBuffer < this._minBufferPx && newRange.end != dataLength) {
|
|
const expandEnd = Math.ceil((this._maxBufferPx - endBuffer) / this._itemSize);
|
|
if (expandEnd > 0) {
|
|
newRange.end = Math.min(dataLength, newRange.end + expandEnd);
|
|
newRange.start = Math.max(0, Math.floor(firstVisibleIndex - this._minBufferPx / this._itemSize));
|
|
}
|
|
}
|
|
}
|
|
this._viewport.setRenderedRange(newRange);
|
|
this._viewport.setRenderedContentOffset(this._itemSize * newRange.start);
|
|
this._scrolledIndexChange.next(Math.floor(firstVisibleIndex));
|
|
}
|
|
};
|
|
function _fixedSizeVirtualScrollStrategyFactory(fixedSizeDir) {
|
|
return fixedSizeDir._scrollStrategy;
|
|
}
|
|
var _CdkFixedSizeVirtualScroll = class _CdkFixedSizeVirtualScroll {
|
|
constructor() {
|
|
this._itemSize = 20;
|
|
this._minBufferPx = 100;
|
|
this._maxBufferPx = 200;
|
|
this._scrollStrategy = new FixedSizeVirtualScrollStrategy(this.itemSize, this.minBufferPx, this.maxBufferPx);
|
|
}
|
|
/** The size of the items in the list (in pixels). */
|
|
get itemSize() {
|
|
return this._itemSize;
|
|
}
|
|
set itemSize(value) {
|
|
this._itemSize = coerceNumberProperty(value);
|
|
}
|
|
/**
|
|
* The minimum amount of buffer rendered beyond the viewport (in pixels).
|
|
* If the amount of buffer dips below this number, more items will be rendered. Defaults to 100px.
|
|
*/
|
|
get minBufferPx() {
|
|
return this._minBufferPx;
|
|
}
|
|
set minBufferPx(value) {
|
|
this._minBufferPx = coerceNumberProperty(value);
|
|
}
|
|
/**
|
|
* The number of pixels worth of buffer to render for when rendering new items. Defaults to 200px.
|
|
*/
|
|
get maxBufferPx() {
|
|
return this._maxBufferPx;
|
|
}
|
|
set maxBufferPx(value) {
|
|
this._maxBufferPx = coerceNumberProperty(value);
|
|
}
|
|
ngOnChanges() {
|
|
this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.minBufferPx, this.maxBufferPx);
|
|
}
|
|
};
|
|
_CdkFixedSizeVirtualScroll.ɵfac = function CdkFixedSizeVirtualScroll_Factory(t) {
|
|
return new (t || _CdkFixedSizeVirtualScroll)();
|
|
};
|
|
_CdkFixedSizeVirtualScroll.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkFixedSizeVirtualScroll,
|
|
selectors: [["cdk-virtual-scroll-viewport", "itemSize", ""]],
|
|
inputs: {
|
|
itemSize: "itemSize",
|
|
minBufferPx: "minBufferPx",
|
|
maxBufferPx: "maxBufferPx"
|
|
},
|
|
standalone: true,
|
|
features: [ɵɵProvidersFeature([{
|
|
provide: VIRTUAL_SCROLL_STRATEGY,
|
|
useFactory: _fixedSizeVirtualScrollStrategyFactory,
|
|
deps: [forwardRef(() => _CdkFixedSizeVirtualScroll)]
|
|
}]), ɵɵNgOnChangesFeature]
|
|
});
|
|
var CdkFixedSizeVirtualScroll = _CdkFixedSizeVirtualScroll;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkFixedSizeVirtualScroll, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "cdk-virtual-scroll-viewport[itemSize]",
|
|
standalone: true,
|
|
providers: [{
|
|
provide: VIRTUAL_SCROLL_STRATEGY,
|
|
useFactory: _fixedSizeVirtualScrollStrategyFactory,
|
|
deps: [forwardRef(() => CdkFixedSizeVirtualScroll)]
|
|
}]
|
|
}]
|
|
}], null, {
|
|
itemSize: [{
|
|
type: Input
|
|
}],
|
|
minBufferPx: [{
|
|
type: Input
|
|
}],
|
|
maxBufferPx: [{
|
|
type: Input
|
|
}]
|
|
});
|
|
})();
|
|
var DEFAULT_SCROLL_TIME = 20;
|
|
var _ScrollDispatcher = class _ScrollDispatcher {
|
|
constructor(_ngZone, _platform, document2) {
|
|
this._ngZone = _ngZone;
|
|
this._platform = _platform;
|
|
this._scrolled = new Subject();
|
|
this._globalSubscription = null;
|
|
this._scrolledCount = 0;
|
|
this.scrollContainers = /* @__PURE__ */ new Map();
|
|
this._document = document2;
|
|
}
|
|
/**
|
|
* Registers a scrollable instance with the service and listens for its scrolled events. When the
|
|
* scrollable is scrolled, the service emits the event to its scrolled observable.
|
|
* @param scrollable Scrollable instance to be registered.
|
|
*/
|
|
register(scrollable) {
|
|
if (!this.scrollContainers.has(scrollable)) {
|
|
this.scrollContainers.set(scrollable, scrollable.elementScrolled().subscribe(() => this._scrolled.next(scrollable)));
|
|
}
|
|
}
|
|
/**
|
|
* De-registers a Scrollable reference and unsubscribes from its scroll event observable.
|
|
* @param scrollable Scrollable instance to be deregistered.
|
|
*/
|
|
deregister(scrollable) {
|
|
const scrollableReference = this.scrollContainers.get(scrollable);
|
|
if (scrollableReference) {
|
|
scrollableReference.unsubscribe();
|
|
this.scrollContainers.delete(scrollable);
|
|
}
|
|
}
|
|
/**
|
|
* Returns an observable that emits an event whenever any of the registered Scrollable
|
|
* references (or window, document, or body) fire a scrolled event. Can provide a time in ms
|
|
* to override the default "throttle" time.
|
|
*
|
|
* **Note:** in order to avoid hitting change detection for every scroll event,
|
|
* all of the events emitted from this stream will be run outside the Angular zone.
|
|
* If you need to update any data bindings as a result of a scroll event, you have
|
|
* to run the callback using `NgZone.run`.
|
|
*/
|
|
scrolled(auditTimeInMs = DEFAULT_SCROLL_TIME) {
|
|
if (!this._platform.isBrowser) {
|
|
return of();
|
|
}
|
|
return new Observable((observer) => {
|
|
if (!this._globalSubscription) {
|
|
this._addGlobalListener();
|
|
}
|
|
const subscription = auditTimeInMs > 0 ? this._scrolled.pipe(auditTime(auditTimeInMs)).subscribe(observer) : this._scrolled.subscribe(observer);
|
|
this._scrolledCount++;
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
this._scrolledCount--;
|
|
if (!this._scrolledCount) {
|
|
this._removeGlobalListener();
|
|
}
|
|
};
|
|
});
|
|
}
|
|
ngOnDestroy() {
|
|
this._removeGlobalListener();
|
|
this.scrollContainers.forEach((_, container) => this.deregister(container));
|
|
this._scrolled.complete();
|
|
}
|
|
/**
|
|
* Returns an observable that emits whenever any of the
|
|
* scrollable ancestors of an element are scrolled.
|
|
* @param elementOrElementRef Element whose ancestors to listen for.
|
|
* @param auditTimeInMs Time to throttle the scroll events.
|
|
*/
|
|
ancestorScrolled(elementOrElementRef, auditTimeInMs) {
|
|
const ancestors = this.getAncestorScrollContainers(elementOrElementRef);
|
|
return this.scrolled(auditTimeInMs).pipe(filter((target) => {
|
|
return !target || ancestors.indexOf(target) > -1;
|
|
}));
|
|
}
|
|
/** Returns all registered Scrollables that contain the provided element. */
|
|
getAncestorScrollContainers(elementOrElementRef) {
|
|
const scrollingContainers = [];
|
|
this.scrollContainers.forEach((_subscription, scrollable) => {
|
|
if (this._scrollableContainsElement(scrollable, elementOrElementRef)) {
|
|
scrollingContainers.push(scrollable);
|
|
}
|
|
});
|
|
return scrollingContainers;
|
|
}
|
|
/** Use defaultView of injected document if available or fallback to global window reference */
|
|
_getWindow() {
|
|
return this._document.defaultView || window;
|
|
}
|
|
/** Returns true if the element is contained within the provided Scrollable. */
|
|
_scrollableContainsElement(scrollable, elementOrElementRef) {
|
|
let element = coerceElement(elementOrElementRef);
|
|
let scrollableElement = scrollable.getElementRef().nativeElement;
|
|
do {
|
|
if (element == scrollableElement) {
|
|
return true;
|
|
}
|
|
} while (element = element.parentElement);
|
|
return false;
|
|
}
|
|
/** Sets up the global scroll listeners. */
|
|
_addGlobalListener() {
|
|
this._globalSubscription = this._ngZone.runOutsideAngular(() => {
|
|
const window2 = this._getWindow();
|
|
return fromEvent(window2.document, "scroll").subscribe(() => this._scrolled.next());
|
|
});
|
|
}
|
|
/** Cleans up the global scroll listener. */
|
|
_removeGlobalListener() {
|
|
if (this._globalSubscription) {
|
|
this._globalSubscription.unsubscribe();
|
|
this._globalSubscription = null;
|
|
}
|
|
}
|
|
};
|
|
_ScrollDispatcher.ɵfac = function ScrollDispatcher_Factory(t) {
|
|
return new (t || _ScrollDispatcher)(ɵɵinject(NgZone), ɵɵinject(Platform), ɵɵinject(DOCUMENT, 8));
|
|
};
|
|
_ScrollDispatcher.ɵprov = ɵɵdefineInjectable({
|
|
token: _ScrollDispatcher,
|
|
factory: _ScrollDispatcher.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var ScrollDispatcher = _ScrollDispatcher;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ScrollDispatcher, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: NgZone
|
|
}, {
|
|
type: Platform
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _CdkScrollable = class _CdkScrollable {
|
|
constructor(elementRef, scrollDispatcher, ngZone, dir) {
|
|
this.elementRef = elementRef;
|
|
this.scrollDispatcher = scrollDispatcher;
|
|
this.ngZone = ngZone;
|
|
this.dir = dir;
|
|
this._destroyed = new Subject();
|
|
this._elementScrolled = new Observable((observer) => this.ngZone.runOutsideAngular(() => fromEvent(this.elementRef.nativeElement, "scroll").pipe(takeUntil(this._destroyed)).subscribe(observer)));
|
|
}
|
|
ngOnInit() {
|
|
this.scrollDispatcher.register(this);
|
|
}
|
|
ngOnDestroy() {
|
|
this.scrollDispatcher.deregister(this);
|
|
this._destroyed.next();
|
|
this._destroyed.complete();
|
|
}
|
|
/** Returns observable that emits when a scroll event is fired on the host element. */
|
|
elementScrolled() {
|
|
return this._elementScrolled;
|
|
}
|
|
/** Gets the ElementRef for the viewport. */
|
|
getElementRef() {
|
|
return this.elementRef;
|
|
}
|
|
/**
|
|
* Scrolls to the specified offsets. This is a normalized version of the browser's native scrollTo
|
|
* method, since browsers are not consistent about what scrollLeft means in RTL. For this method
|
|
* left and right always refer to the left and right side of the scrolling container irrespective
|
|
* of the layout direction. start and end refer to left and right in an LTR context and vice-versa
|
|
* in an RTL context.
|
|
* @param options specified the offsets to scroll to.
|
|
*/
|
|
scrollTo(options) {
|
|
const el = this.elementRef.nativeElement;
|
|
const isRtl = this.dir && this.dir.value == "rtl";
|
|
if (options.left == null) {
|
|
options.left = isRtl ? options.end : options.start;
|
|
}
|
|
if (options.right == null) {
|
|
options.right = isRtl ? options.start : options.end;
|
|
}
|
|
if (options.bottom != null) {
|
|
options.top = el.scrollHeight - el.clientHeight - options.bottom;
|
|
}
|
|
if (isRtl && getRtlScrollAxisType() != 0) {
|
|
if (options.left != null) {
|
|
options.right = el.scrollWidth - el.clientWidth - options.left;
|
|
}
|
|
if (getRtlScrollAxisType() == 2) {
|
|
options.left = options.right;
|
|
} else if (getRtlScrollAxisType() == 1) {
|
|
options.left = options.right ? -options.right : options.right;
|
|
}
|
|
} else {
|
|
if (options.right != null) {
|
|
options.left = el.scrollWidth - el.clientWidth - options.right;
|
|
}
|
|
}
|
|
this._applyScrollToOptions(options);
|
|
}
|
|
_applyScrollToOptions(options) {
|
|
const el = this.elementRef.nativeElement;
|
|
if (supportsScrollBehavior()) {
|
|
el.scrollTo(options);
|
|
} else {
|
|
if (options.top != null) {
|
|
el.scrollTop = options.top;
|
|
}
|
|
if (options.left != null) {
|
|
el.scrollLeft = options.left;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Measures the scroll offset relative to the specified edge of the viewport. This method can be
|
|
* used instead of directly checking scrollLeft or scrollTop, since browsers are not consistent
|
|
* about what scrollLeft means in RTL. The values returned by this method are normalized such that
|
|
* left and right always refer to the left and right side of the scrolling container irrespective
|
|
* of the layout direction. start and end refer to left and right in an LTR context and vice-versa
|
|
* in an RTL context.
|
|
* @param from The edge to measure from.
|
|
*/
|
|
measureScrollOffset(from) {
|
|
const LEFT = "left";
|
|
const RIGHT = "right";
|
|
const el = this.elementRef.nativeElement;
|
|
if (from == "top") {
|
|
return el.scrollTop;
|
|
}
|
|
if (from == "bottom") {
|
|
return el.scrollHeight - el.clientHeight - el.scrollTop;
|
|
}
|
|
const isRtl = this.dir && this.dir.value == "rtl";
|
|
if (from == "start") {
|
|
from = isRtl ? RIGHT : LEFT;
|
|
} else if (from == "end") {
|
|
from = isRtl ? LEFT : RIGHT;
|
|
}
|
|
if (isRtl && getRtlScrollAxisType() == 2) {
|
|
if (from == LEFT) {
|
|
return el.scrollWidth - el.clientWidth - el.scrollLeft;
|
|
} else {
|
|
return el.scrollLeft;
|
|
}
|
|
} else if (isRtl && getRtlScrollAxisType() == 1) {
|
|
if (from == LEFT) {
|
|
return el.scrollLeft + el.scrollWidth - el.clientWidth;
|
|
} else {
|
|
return -el.scrollLeft;
|
|
}
|
|
} else {
|
|
if (from == LEFT) {
|
|
return el.scrollLeft;
|
|
} else {
|
|
return el.scrollWidth - el.clientWidth - el.scrollLeft;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
_CdkScrollable.ɵfac = function CdkScrollable_Factory(t) {
|
|
return new (t || _CdkScrollable)(ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(ScrollDispatcher), ɵɵdirectiveInject(NgZone), ɵɵdirectiveInject(Directionality, 8));
|
|
};
|
|
_CdkScrollable.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkScrollable,
|
|
selectors: [["", "cdk-scrollable", ""], ["", "cdkScrollable", ""]],
|
|
standalone: true
|
|
});
|
|
var CdkScrollable = _CdkScrollable;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkScrollable, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdk-scrollable], [cdkScrollable]",
|
|
standalone: true
|
|
}]
|
|
}], () => [{
|
|
type: ElementRef
|
|
}, {
|
|
type: ScrollDispatcher
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: Directionality,
|
|
decorators: [{
|
|
type: Optional
|
|
}]
|
|
}], null);
|
|
})();
|
|
var DEFAULT_RESIZE_TIME = 20;
|
|
var _ViewportRuler = class _ViewportRuler {
|
|
constructor(_platform, ngZone, document2) {
|
|
this._platform = _platform;
|
|
this._change = new Subject();
|
|
this._changeListener = (event) => {
|
|
this._change.next(event);
|
|
};
|
|
this._document = document2;
|
|
ngZone.runOutsideAngular(() => {
|
|
if (_platform.isBrowser) {
|
|
const window2 = this._getWindow();
|
|
window2.addEventListener("resize", this._changeListener);
|
|
window2.addEventListener("orientationchange", this._changeListener);
|
|
}
|
|
this.change().subscribe(() => this._viewportSize = null);
|
|
});
|
|
}
|
|
ngOnDestroy() {
|
|
if (this._platform.isBrowser) {
|
|
const window2 = this._getWindow();
|
|
window2.removeEventListener("resize", this._changeListener);
|
|
window2.removeEventListener("orientationchange", this._changeListener);
|
|
}
|
|
this._change.complete();
|
|
}
|
|
/** Returns the viewport's width and height. */
|
|
getViewportSize() {
|
|
if (!this._viewportSize) {
|
|
this._updateViewportSize();
|
|
}
|
|
const output = {
|
|
width: this._viewportSize.width,
|
|
height: this._viewportSize.height
|
|
};
|
|
if (!this._platform.isBrowser) {
|
|
this._viewportSize = null;
|
|
}
|
|
return output;
|
|
}
|
|
/** Gets a ClientRect for the viewport's bounds. */
|
|
getViewportRect() {
|
|
const scrollPosition = this.getViewportScrollPosition();
|
|
const {
|
|
width,
|
|
height
|
|
} = this.getViewportSize();
|
|
return {
|
|
top: scrollPosition.top,
|
|
left: scrollPosition.left,
|
|
bottom: scrollPosition.top + height,
|
|
right: scrollPosition.left + width,
|
|
height,
|
|
width
|
|
};
|
|
}
|
|
/** Gets the (top, left) scroll position of the viewport. */
|
|
getViewportScrollPosition() {
|
|
if (!this._platform.isBrowser) {
|
|
return {
|
|
top: 0,
|
|
left: 0
|
|
};
|
|
}
|
|
const document2 = this._document;
|
|
const window2 = this._getWindow();
|
|
const documentElement = document2.documentElement;
|
|
const documentRect = documentElement.getBoundingClientRect();
|
|
const top = -documentRect.top || document2.body.scrollTop || window2.scrollY || documentElement.scrollTop || 0;
|
|
const left = -documentRect.left || document2.body.scrollLeft || window2.scrollX || documentElement.scrollLeft || 0;
|
|
return {
|
|
top,
|
|
left
|
|
};
|
|
}
|
|
/**
|
|
* Returns a stream that emits whenever the size of the viewport changes.
|
|
* This stream emits outside of the Angular zone.
|
|
* @param throttleTime Time in milliseconds to throttle the stream.
|
|
*/
|
|
change(throttleTime = DEFAULT_RESIZE_TIME) {
|
|
return throttleTime > 0 ? this._change.pipe(auditTime(throttleTime)) : this._change;
|
|
}
|
|
/** Use defaultView of injected document if available or fallback to global window reference */
|
|
_getWindow() {
|
|
return this._document.defaultView || window;
|
|
}
|
|
/** Updates the cached viewport size. */
|
|
_updateViewportSize() {
|
|
const window2 = this._getWindow();
|
|
this._viewportSize = this._platform.isBrowser ? {
|
|
width: window2.innerWidth,
|
|
height: window2.innerHeight
|
|
} : {
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
}
|
|
};
|
|
_ViewportRuler.ɵfac = function ViewportRuler_Factory(t) {
|
|
return new (t || _ViewportRuler)(ɵɵinject(Platform), ɵɵinject(NgZone), ɵɵinject(DOCUMENT, 8));
|
|
};
|
|
_ViewportRuler.ɵprov = ɵɵdefineInjectable({
|
|
token: _ViewportRuler,
|
|
factory: _ViewportRuler.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var ViewportRuler = _ViewportRuler;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ViewportRuler, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: Platform
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var VIRTUAL_SCROLLABLE = new InjectionToken("VIRTUAL_SCROLLABLE");
|
|
var _CdkVirtualScrollable = class _CdkVirtualScrollable extends CdkScrollable {
|
|
constructor(elementRef, scrollDispatcher, ngZone, dir) {
|
|
super(elementRef, scrollDispatcher, ngZone, dir);
|
|
}
|
|
/**
|
|
* Measure the viewport size for the provided orientation.
|
|
*
|
|
* @param orientation The orientation to measure the size from.
|
|
*/
|
|
measureViewportSize(orientation) {
|
|
const viewportEl = this.elementRef.nativeElement;
|
|
return orientation === "horizontal" ? viewportEl.clientWidth : viewportEl.clientHeight;
|
|
}
|
|
};
|
|
_CdkVirtualScrollable.ɵfac = function CdkVirtualScrollable_Factory(t) {
|
|
return new (t || _CdkVirtualScrollable)(ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(ScrollDispatcher), ɵɵdirectiveInject(NgZone), ɵɵdirectiveInject(Directionality, 8));
|
|
};
|
|
_CdkVirtualScrollable.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkVirtualScrollable,
|
|
features: [ɵɵInheritDefinitionFeature]
|
|
});
|
|
var CdkVirtualScrollable = _CdkVirtualScrollable;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkVirtualScrollable, [{
|
|
type: Directive
|
|
}], () => [{
|
|
type: ElementRef
|
|
}, {
|
|
type: ScrollDispatcher
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: Directionality,
|
|
decorators: [{
|
|
type: Optional
|
|
}]
|
|
}], null);
|
|
})();
|
|
function rangesEqual(r1, r2) {
|
|
return r1.start == r2.start && r1.end == r2.end;
|
|
}
|
|
var SCROLL_SCHEDULER = typeof requestAnimationFrame !== "undefined" ? animationFrameScheduler : asapScheduler;
|
|
var _CdkVirtualScrollViewport = class _CdkVirtualScrollViewport extends CdkVirtualScrollable {
|
|
/** The direction the viewport scrolls. */
|
|
get orientation() {
|
|
return this._orientation;
|
|
}
|
|
set orientation(orientation) {
|
|
if (this._orientation !== orientation) {
|
|
this._orientation = orientation;
|
|
this._calculateSpacerSize();
|
|
}
|
|
}
|
|
constructor(elementRef, _changeDetectorRef, ngZone, _scrollStrategy, dir, scrollDispatcher, viewportRuler, scrollable) {
|
|
super(elementRef, scrollDispatcher, ngZone, dir);
|
|
this.elementRef = elementRef;
|
|
this._changeDetectorRef = _changeDetectorRef;
|
|
this._scrollStrategy = _scrollStrategy;
|
|
this.scrollable = scrollable;
|
|
this._platform = inject(Platform);
|
|
this._detachedSubject = new Subject();
|
|
this._renderedRangeSubject = new Subject();
|
|
this._orientation = "vertical";
|
|
this.appendOnly = false;
|
|
this.scrolledIndexChange = new Observable((observer) => this._scrollStrategy.scrolledIndexChange.subscribe((index) => Promise.resolve().then(() => this.ngZone.run(() => observer.next(index)))));
|
|
this.renderedRangeStream = this._renderedRangeSubject;
|
|
this._totalContentSize = 0;
|
|
this._totalContentWidth = "";
|
|
this._totalContentHeight = "";
|
|
this._renderedRange = {
|
|
start: 0,
|
|
end: 0
|
|
};
|
|
this._dataLength = 0;
|
|
this._viewportSize = 0;
|
|
this._renderedContentOffset = 0;
|
|
this._renderedContentOffsetNeedsRewrite = false;
|
|
this._isChangeDetectionPending = false;
|
|
this._runAfterChangeDetection = [];
|
|
this._viewportChanges = Subscription.EMPTY;
|
|
if (!_scrollStrategy && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error('Error: cdk-virtual-scroll-viewport requires the "itemSize" property to be set.');
|
|
}
|
|
this._viewportChanges = viewportRuler.change().subscribe(() => {
|
|
this.checkViewportSize();
|
|
});
|
|
if (!this.scrollable) {
|
|
this.elementRef.nativeElement.classList.add("cdk-virtual-scrollable");
|
|
this.scrollable = this;
|
|
}
|
|
}
|
|
ngOnInit() {
|
|
if (!this._platform.isBrowser) {
|
|
return;
|
|
}
|
|
if (this.scrollable === this) {
|
|
super.ngOnInit();
|
|
}
|
|
this.ngZone.runOutsideAngular(() => Promise.resolve().then(() => {
|
|
this._measureViewportSize();
|
|
this._scrollStrategy.attach(this);
|
|
this.scrollable.elementScrolled().pipe(
|
|
// Start off with a fake scroll event so we properly detect our initial position.
|
|
startWith(null),
|
|
// Collect multiple events into one until the next animation frame. This way if
|
|
// there are multiple scroll events in the same frame we only need to recheck
|
|
// our layout once.
|
|
auditTime(0, SCROLL_SCHEDULER),
|
|
// Usually `elementScrolled` is completed when the scrollable is destroyed, but
|
|
// that may not be the case if a `CdkVirtualScrollableElement` is used so we have
|
|
// to unsubscribe here just in case.
|
|
takeUntil(this._destroyed)
|
|
).subscribe(() => this._scrollStrategy.onContentScrolled());
|
|
this._markChangeDetectionNeeded();
|
|
}));
|
|
}
|
|
ngOnDestroy() {
|
|
this.detach();
|
|
this._scrollStrategy.detach();
|
|
this._renderedRangeSubject.complete();
|
|
this._detachedSubject.complete();
|
|
this._viewportChanges.unsubscribe();
|
|
super.ngOnDestroy();
|
|
}
|
|
/** Attaches a `CdkVirtualScrollRepeater` to this viewport. */
|
|
attach(forOf) {
|
|
if (this._forOf && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error("CdkVirtualScrollViewport is already attached.");
|
|
}
|
|
this.ngZone.runOutsideAngular(() => {
|
|
this._forOf = forOf;
|
|
this._forOf.dataStream.pipe(takeUntil(this._detachedSubject)).subscribe((data) => {
|
|
const newLength = data.length;
|
|
if (newLength !== this._dataLength) {
|
|
this._dataLength = newLength;
|
|
this._scrollStrategy.onDataLengthChanged();
|
|
}
|
|
this._doChangeDetection();
|
|
});
|
|
});
|
|
}
|
|
/** Detaches the current `CdkVirtualForOf`. */
|
|
detach() {
|
|
this._forOf = null;
|
|
this._detachedSubject.next();
|
|
}
|
|
/** Gets the length of the data bound to this viewport (in number of items). */
|
|
getDataLength() {
|
|
return this._dataLength;
|
|
}
|
|
/** Gets the size of the viewport (in pixels). */
|
|
getViewportSize() {
|
|
return this._viewportSize;
|
|
}
|
|
// TODO(mmalerba): This is technically out of sync with what's really rendered until a render
|
|
// cycle happens. I'm being careful to only call it after the render cycle is complete and before
|
|
// setting it to something else, but its error prone and should probably be split into
|
|
// `pendingRange` and `renderedRange`, the latter reflecting whats actually in the DOM.
|
|
/** Get the current rendered range of items. */
|
|
getRenderedRange() {
|
|
return this._renderedRange;
|
|
}
|
|
measureBoundingClientRectWithScrollOffset(from) {
|
|
return this.getElementRef().nativeElement.getBoundingClientRect()[from];
|
|
}
|
|
/**
|
|
* Sets the total size of all content (in pixels), including content that is not currently
|
|
* rendered.
|
|
*/
|
|
setTotalContentSize(size) {
|
|
if (this._totalContentSize !== size) {
|
|
this._totalContentSize = size;
|
|
this._calculateSpacerSize();
|
|
this._markChangeDetectionNeeded();
|
|
}
|
|
}
|
|
/** Sets the currently rendered range of indices. */
|
|
setRenderedRange(range) {
|
|
if (!rangesEqual(this._renderedRange, range)) {
|
|
if (this.appendOnly) {
|
|
range = {
|
|
start: 0,
|
|
end: Math.max(this._renderedRange.end, range.end)
|
|
};
|
|
}
|
|
this._renderedRangeSubject.next(this._renderedRange = range);
|
|
this._markChangeDetectionNeeded(() => this._scrollStrategy.onContentRendered());
|
|
}
|
|
}
|
|
/**
|
|
* Gets the offset from the start of the viewport to the start of the rendered data (in pixels).
|
|
*/
|
|
getOffsetToRenderedContentStart() {
|
|
return this._renderedContentOffsetNeedsRewrite ? null : this._renderedContentOffset;
|
|
}
|
|
/**
|
|
* Sets the offset from the start of the viewport to either the start or end of the rendered data
|
|
* (in pixels).
|
|
*/
|
|
setRenderedContentOffset(offset, to = "to-start") {
|
|
offset = this.appendOnly && to === "to-start" ? 0 : offset;
|
|
const isRtl = this.dir && this.dir.value == "rtl";
|
|
const isHorizontal = this.orientation == "horizontal";
|
|
const axis = isHorizontal ? "X" : "Y";
|
|
const axisDirection = isHorizontal && isRtl ? -1 : 1;
|
|
let transform = `translate${axis}(${Number(axisDirection * offset)}px)`;
|
|
this._renderedContentOffset = offset;
|
|
if (to === "to-end") {
|
|
transform += ` translate${axis}(-100%)`;
|
|
this._renderedContentOffsetNeedsRewrite = true;
|
|
}
|
|
if (this._renderedContentTransform != transform) {
|
|
this._renderedContentTransform = transform;
|
|
this._markChangeDetectionNeeded(() => {
|
|
if (this._renderedContentOffsetNeedsRewrite) {
|
|
this._renderedContentOffset -= this.measureRenderedContentSize();
|
|
this._renderedContentOffsetNeedsRewrite = false;
|
|
this.setRenderedContentOffset(this._renderedContentOffset);
|
|
} else {
|
|
this._scrollStrategy.onRenderedOffsetChanged();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Scrolls to the given offset from the start of the viewport. Please note that this is not always
|
|
* the same as setting `scrollTop` or `scrollLeft`. In a horizontal viewport with right-to-left
|
|
* direction, this would be the equivalent of setting a fictional `scrollRight` property.
|
|
* @param offset The offset to scroll to.
|
|
* @param behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
|
|
*/
|
|
scrollToOffset(offset, behavior = "auto") {
|
|
const options = {
|
|
behavior
|
|
};
|
|
if (this.orientation === "horizontal") {
|
|
options.start = offset;
|
|
} else {
|
|
options.top = offset;
|
|
}
|
|
this.scrollable.scrollTo(options);
|
|
}
|
|
/**
|
|
* Scrolls to the offset for the given index.
|
|
* @param index The index of the element to scroll to.
|
|
* @param behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
|
|
*/
|
|
scrollToIndex(index, behavior = "auto") {
|
|
this._scrollStrategy.scrollToIndex(index, behavior);
|
|
}
|
|
/**
|
|
* Gets the current scroll offset from the start of the scrollable (in pixels).
|
|
* @param from The edge to measure the offset from. Defaults to 'top' in vertical mode and 'start'
|
|
* in horizontal mode.
|
|
*/
|
|
measureScrollOffset(from) {
|
|
let measureScrollOffset;
|
|
if (this.scrollable == this) {
|
|
measureScrollOffset = (_from) => super.measureScrollOffset(_from);
|
|
} else {
|
|
measureScrollOffset = (_from) => this.scrollable.measureScrollOffset(_from);
|
|
}
|
|
return Math.max(0, measureScrollOffset(from ?? (this.orientation === "horizontal" ? "start" : "top")) - this.measureViewportOffset());
|
|
}
|
|
/**
|
|
* Measures the offset of the viewport from the scrolling container
|
|
* @param from The edge to measure from.
|
|
*/
|
|
measureViewportOffset(from) {
|
|
let fromRect;
|
|
const LEFT = "left";
|
|
const RIGHT = "right";
|
|
const isRtl = this.dir?.value == "rtl";
|
|
if (from == "start") {
|
|
fromRect = isRtl ? RIGHT : LEFT;
|
|
} else if (from == "end") {
|
|
fromRect = isRtl ? LEFT : RIGHT;
|
|
} else if (from) {
|
|
fromRect = from;
|
|
} else {
|
|
fromRect = this.orientation === "horizontal" ? "left" : "top";
|
|
}
|
|
const scrollerClientRect = this.scrollable.measureBoundingClientRectWithScrollOffset(fromRect);
|
|
const viewportClientRect = this.elementRef.nativeElement.getBoundingClientRect()[fromRect];
|
|
return viewportClientRect - scrollerClientRect;
|
|
}
|
|
/** Measure the combined size of all of the rendered items. */
|
|
measureRenderedContentSize() {
|
|
const contentEl = this._contentWrapper.nativeElement;
|
|
return this.orientation === "horizontal" ? contentEl.offsetWidth : contentEl.offsetHeight;
|
|
}
|
|
/**
|
|
* Measure the total combined size of the given range. Throws if the range includes items that are
|
|
* not rendered.
|
|
*/
|
|
measureRangeSize(range) {
|
|
if (!this._forOf) {
|
|
return 0;
|
|
}
|
|
return this._forOf.measureRangeSize(range, this.orientation);
|
|
}
|
|
/** Update the viewport dimensions and re-render. */
|
|
checkViewportSize() {
|
|
this._measureViewportSize();
|
|
this._scrollStrategy.onDataLengthChanged();
|
|
}
|
|
/** Measure the viewport size. */
|
|
_measureViewportSize() {
|
|
this._viewportSize = this.scrollable.measureViewportSize(this.orientation);
|
|
}
|
|
/** Queue up change detection to run. */
|
|
_markChangeDetectionNeeded(runAfter) {
|
|
if (runAfter) {
|
|
this._runAfterChangeDetection.push(runAfter);
|
|
}
|
|
if (!this._isChangeDetectionPending) {
|
|
this._isChangeDetectionPending = true;
|
|
this.ngZone.runOutsideAngular(() => Promise.resolve().then(() => {
|
|
this._doChangeDetection();
|
|
}));
|
|
}
|
|
}
|
|
/** Run change detection. */
|
|
_doChangeDetection() {
|
|
this._isChangeDetectionPending = false;
|
|
this._contentWrapper.nativeElement.style.transform = this._renderedContentTransform;
|
|
this.ngZone.run(() => this._changeDetectorRef.markForCheck());
|
|
const runAfterChangeDetection = this._runAfterChangeDetection;
|
|
this._runAfterChangeDetection = [];
|
|
for (const fn of runAfterChangeDetection) {
|
|
fn();
|
|
}
|
|
}
|
|
/** Calculates the `style.width` and `style.height` for the spacer element. */
|
|
_calculateSpacerSize() {
|
|
this._totalContentHeight = this.orientation === "horizontal" ? "" : `${this._totalContentSize}px`;
|
|
this._totalContentWidth = this.orientation === "horizontal" ? `${this._totalContentSize}px` : "";
|
|
}
|
|
};
|
|
_CdkVirtualScrollViewport.ɵfac = function CdkVirtualScrollViewport_Factory(t) {
|
|
return new (t || _CdkVirtualScrollViewport)(ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(ChangeDetectorRef), ɵɵdirectiveInject(NgZone), ɵɵdirectiveInject(VIRTUAL_SCROLL_STRATEGY, 8), ɵɵdirectiveInject(Directionality, 8), ɵɵdirectiveInject(ScrollDispatcher), ɵɵdirectiveInject(ViewportRuler), ɵɵdirectiveInject(VIRTUAL_SCROLLABLE, 8));
|
|
};
|
|
_CdkVirtualScrollViewport.ɵcmp = ɵɵdefineComponent({
|
|
type: _CdkVirtualScrollViewport,
|
|
selectors: [["cdk-virtual-scroll-viewport"]],
|
|
viewQuery: function CdkVirtualScrollViewport_Query(rf, ctx) {
|
|
if (rf & 1) {
|
|
ɵɵviewQuery(_c0, 7);
|
|
}
|
|
if (rf & 2) {
|
|
let _t;
|
|
ɵɵqueryRefresh(_t = ɵɵloadQuery()) && (ctx._contentWrapper = _t.first);
|
|
}
|
|
},
|
|
hostAttrs: [1, "cdk-virtual-scroll-viewport"],
|
|
hostVars: 4,
|
|
hostBindings: function CdkVirtualScrollViewport_HostBindings(rf, ctx) {
|
|
if (rf & 2) {
|
|
ɵɵclassProp("cdk-virtual-scroll-orientation-horizontal", ctx.orientation === "horizontal")("cdk-virtual-scroll-orientation-vertical", ctx.orientation !== "horizontal");
|
|
}
|
|
},
|
|
inputs: {
|
|
orientation: "orientation",
|
|
appendOnly: ["appendOnly", "appendOnly", booleanAttribute]
|
|
},
|
|
outputs: {
|
|
scrolledIndexChange: "scrolledIndexChange"
|
|
},
|
|
standalone: true,
|
|
features: [ɵɵProvidersFeature([{
|
|
provide: CdkScrollable,
|
|
useFactory: (virtualScrollable, viewport) => virtualScrollable || viewport,
|
|
deps: [[new Optional(), new Inject(VIRTUAL_SCROLLABLE)], _CdkVirtualScrollViewport]
|
|
}]), ɵɵInputTransformsFeature, ɵɵInheritDefinitionFeature, ɵɵStandaloneFeature],
|
|
ngContentSelectors: _c1,
|
|
decls: 4,
|
|
vars: 4,
|
|
consts: [[1, "cdk-virtual-scroll-content-wrapper"], ["contentWrapper", ""], [1, "cdk-virtual-scroll-spacer"]],
|
|
template: function CdkVirtualScrollViewport_Template(rf, ctx) {
|
|
if (rf & 1) {
|
|
ɵɵprojectionDef();
|
|
ɵɵelementStart(0, "div", 0, 1);
|
|
ɵɵprojection(2);
|
|
ɵɵelementEnd();
|
|
ɵɵelement(3, "div", 2);
|
|
}
|
|
if (rf & 2) {
|
|
ɵɵadvance(3);
|
|
ɵɵstyleProp("width", ctx._totalContentWidth)("height", ctx._totalContentHeight);
|
|
}
|
|
},
|
|
styles: ["cdk-virtual-scroll-viewport{display:block;position:relative;transform:translateZ(0)}.cdk-virtual-scrollable{overflow:auto;will-change:scroll-position;contain:strict;-webkit-overflow-scrolling:touch}.cdk-virtual-scroll-content-wrapper{position:absolute;top:0;left:0;contain:content}[dir=rtl] .cdk-virtual-scroll-content-wrapper{right:0;left:auto}.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper{min-height:100%}.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>dl:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>ol:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>table:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>ul:not([cdkVirtualFor]){padding-left:0;padding-right:0;margin-left:0;margin-right:0;border-left-width:0;border-right-width:0;outline:none}.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper{min-width:100%}.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>dl:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>ol:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>table:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>ul:not([cdkVirtualFor]){padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;border-top-width:0;border-bottom-width:0;outline:none}.cdk-virtual-scroll-spacer{height:1px;transform-origin:0 0;flex:0 0 auto}[dir=rtl] .cdk-virtual-scroll-spacer{transform-origin:100% 0}"],
|
|
encapsulation: 2,
|
|
changeDetection: 0
|
|
});
|
|
var CdkVirtualScrollViewport = _CdkVirtualScrollViewport;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkVirtualScrollViewport, [{
|
|
type: Component,
|
|
args: [{
|
|
selector: "cdk-virtual-scroll-viewport",
|
|
host: {
|
|
"class": "cdk-virtual-scroll-viewport",
|
|
"[class.cdk-virtual-scroll-orientation-horizontal]": 'orientation === "horizontal"',
|
|
"[class.cdk-virtual-scroll-orientation-vertical]": 'orientation !== "horizontal"'
|
|
},
|
|
encapsulation: ViewEncapsulation$1.None,
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
standalone: true,
|
|
providers: [{
|
|
provide: CdkScrollable,
|
|
useFactory: (virtualScrollable, viewport) => virtualScrollable || viewport,
|
|
deps: [[new Optional(), new Inject(VIRTUAL_SCROLLABLE)], CdkVirtualScrollViewport]
|
|
}],
|
|
template: '<!--\n Wrap the rendered content in an element that will be used to offset it based on the scroll\n position.\n-->\n<div #contentWrapper class="cdk-virtual-scroll-content-wrapper">\n <ng-content></ng-content>\n</div>\n<!--\n Spacer used to force the scrolling container to the correct size for the *total* number of items\n so that the scrollbar captures the size of the entire data set.\n-->\n<div class="cdk-virtual-scroll-spacer"\n [style.width]="_totalContentWidth" [style.height]="_totalContentHeight"></div>\n',
|
|
styles: ["cdk-virtual-scroll-viewport{display:block;position:relative;transform:translateZ(0)}.cdk-virtual-scrollable{overflow:auto;will-change:scroll-position;contain:strict;-webkit-overflow-scrolling:touch}.cdk-virtual-scroll-content-wrapper{position:absolute;top:0;left:0;contain:content}[dir=rtl] .cdk-virtual-scroll-content-wrapper{right:0;left:auto}.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper{min-height:100%}.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>dl:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>ol:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>table:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>ul:not([cdkVirtualFor]){padding-left:0;padding-right:0;margin-left:0;margin-right:0;border-left-width:0;border-right-width:0;outline:none}.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper{min-width:100%}.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>dl:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>ol:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>table:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>ul:not([cdkVirtualFor]){padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;border-top-width:0;border-bottom-width:0;outline:none}.cdk-virtual-scroll-spacer{height:1px;transform-origin:0 0;flex:0 0 auto}[dir=rtl] .cdk-virtual-scroll-spacer{transform-origin:100% 0}"]
|
|
}]
|
|
}], () => [{
|
|
type: ElementRef
|
|
}, {
|
|
type: ChangeDetectorRef
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [VIRTUAL_SCROLL_STRATEGY]
|
|
}]
|
|
}, {
|
|
type: Directionality,
|
|
decorators: [{
|
|
type: Optional
|
|
}]
|
|
}, {
|
|
type: ScrollDispatcher
|
|
}, {
|
|
type: ViewportRuler
|
|
}, {
|
|
type: CdkVirtualScrollable,
|
|
decorators: [{
|
|
type: Optional
|
|
}, {
|
|
type: Inject,
|
|
args: [VIRTUAL_SCROLLABLE]
|
|
}]
|
|
}], {
|
|
orientation: [{
|
|
type: Input
|
|
}],
|
|
appendOnly: [{
|
|
type: Input,
|
|
args: [{
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
scrolledIndexChange: [{
|
|
type: Output
|
|
}],
|
|
_contentWrapper: [{
|
|
type: ViewChild,
|
|
args: ["contentWrapper", {
|
|
static: true
|
|
}]
|
|
}]
|
|
});
|
|
})();
|
|
function getOffset(orientation, direction, node) {
|
|
const el = node;
|
|
if (!el.getBoundingClientRect) {
|
|
return 0;
|
|
}
|
|
const rect = el.getBoundingClientRect();
|
|
if (orientation === "horizontal") {
|
|
return direction === "start" ? rect.left : rect.right;
|
|
}
|
|
return direction === "start" ? rect.top : rect.bottom;
|
|
}
|
|
var _CdkVirtualForOf = class _CdkVirtualForOf {
|
|
/** The DataSource to display. */
|
|
get cdkVirtualForOf() {
|
|
return this._cdkVirtualForOf;
|
|
}
|
|
set cdkVirtualForOf(value) {
|
|
this._cdkVirtualForOf = value;
|
|
if (isDataSource(value)) {
|
|
this._dataSourceChanges.next(value);
|
|
} else {
|
|
this._dataSourceChanges.next(new ArrayDataSource(isObservable(value) ? value : Array.from(value || [])));
|
|
}
|
|
}
|
|
/**
|
|
* The `TrackByFunction` to use for tracking changes. The `TrackByFunction` takes the index and
|
|
* the item and produces a value to be used as the item's identity when tracking changes.
|
|
*/
|
|
get cdkVirtualForTrackBy() {
|
|
return this._cdkVirtualForTrackBy;
|
|
}
|
|
set cdkVirtualForTrackBy(fn) {
|
|
this._needsUpdate = true;
|
|
this._cdkVirtualForTrackBy = fn ? (index, item) => fn(index + (this._renderedRange ? this._renderedRange.start : 0), item) : void 0;
|
|
}
|
|
/** The template used to stamp out new elements. */
|
|
set cdkVirtualForTemplate(value) {
|
|
if (value) {
|
|
this._needsUpdate = true;
|
|
this._template = value;
|
|
}
|
|
}
|
|
/**
|
|
* The size of the cache used to store templates that are not being used for re-use later.
|
|
* Setting the cache size to `0` will disable caching. Defaults to 20 templates.
|
|
*/
|
|
get cdkVirtualForTemplateCacheSize() {
|
|
return this._viewRepeater.viewCacheSize;
|
|
}
|
|
set cdkVirtualForTemplateCacheSize(size) {
|
|
this._viewRepeater.viewCacheSize = coerceNumberProperty(size);
|
|
}
|
|
constructor(_viewContainerRef, _template, _differs, _viewRepeater, _viewport, ngZone) {
|
|
this._viewContainerRef = _viewContainerRef;
|
|
this._template = _template;
|
|
this._differs = _differs;
|
|
this._viewRepeater = _viewRepeater;
|
|
this._viewport = _viewport;
|
|
this.viewChange = new Subject();
|
|
this._dataSourceChanges = new Subject();
|
|
this.dataStream = this._dataSourceChanges.pipe(
|
|
// Start off with null `DataSource`.
|
|
startWith(null),
|
|
// Bundle up the previous and current data sources so we can work with both.
|
|
pairwise(),
|
|
// Use `_changeDataSource` to disconnect from the previous data source and connect to the
|
|
// new one, passing back a stream of data changes which we run through `switchMap` to give
|
|
// us a data stream that emits the latest data from whatever the current `DataSource` is.
|
|
switchMap(([prev, cur]) => this._changeDataSource(prev, cur)),
|
|
// Replay the last emitted data when someone subscribes.
|
|
shareReplay(1)
|
|
);
|
|
this._differ = null;
|
|
this._needsUpdate = false;
|
|
this._destroyed = new Subject();
|
|
this.dataStream.subscribe((data) => {
|
|
this._data = data;
|
|
this._onRenderedDataChange();
|
|
});
|
|
this._viewport.renderedRangeStream.pipe(takeUntil(this._destroyed)).subscribe((range) => {
|
|
this._renderedRange = range;
|
|
if (this.viewChange.observers.length) {
|
|
ngZone.run(() => this.viewChange.next(this._renderedRange));
|
|
}
|
|
this._onRenderedDataChange();
|
|
});
|
|
this._viewport.attach(this);
|
|
}
|
|
/**
|
|
* Measures the combined size (width for horizontal orientation, height for vertical) of all items
|
|
* in the specified range. Throws an error if the range includes items that are not currently
|
|
* rendered.
|
|
*/
|
|
measureRangeSize(range, orientation) {
|
|
if (range.start >= range.end) {
|
|
return 0;
|
|
}
|
|
if ((range.start < this._renderedRange.start || range.end > this._renderedRange.end) && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error(`Error: attempted to measure an item that isn't rendered.`);
|
|
}
|
|
const renderedStartIndex = range.start - this._renderedRange.start;
|
|
const rangeLen = range.end - range.start;
|
|
let firstNode;
|
|
let lastNode;
|
|
for (let i = 0; i < rangeLen; i++) {
|
|
const view = this._viewContainerRef.get(i + renderedStartIndex);
|
|
if (view && view.rootNodes.length) {
|
|
firstNode = lastNode = view.rootNodes[0];
|
|
break;
|
|
}
|
|
}
|
|
for (let i = rangeLen - 1; i > -1; i--) {
|
|
const view = this._viewContainerRef.get(i + renderedStartIndex);
|
|
if (view && view.rootNodes.length) {
|
|
lastNode = view.rootNodes[view.rootNodes.length - 1];
|
|
break;
|
|
}
|
|
}
|
|
return firstNode && lastNode ? getOffset(orientation, "end", lastNode) - getOffset(orientation, "start", firstNode) : 0;
|
|
}
|
|
ngDoCheck() {
|
|
if (this._differ && this._needsUpdate) {
|
|
const changes = this._differ.diff(this._renderedItems);
|
|
if (!changes) {
|
|
this._updateContext();
|
|
} else {
|
|
this._applyChanges(changes);
|
|
}
|
|
this._needsUpdate = false;
|
|
}
|
|
}
|
|
ngOnDestroy() {
|
|
this._viewport.detach();
|
|
this._dataSourceChanges.next(void 0);
|
|
this._dataSourceChanges.complete();
|
|
this.viewChange.complete();
|
|
this._destroyed.next();
|
|
this._destroyed.complete();
|
|
this._viewRepeater.detach();
|
|
}
|
|
/** React to scroll state changes in the viewport. */
|
|
_onRenderedDataChange() {
|
|
if (!this._renderedRange) {
|
|
return;
|
|
}
|
|
this._renderedItems = this._data.slice(this._renderedRange.start, this._renderedRange.end);
|
|
if (!this._differ) {
|
|
this._differ = this._differs.find(this._renderedItems).create((index, item) => {
|
|
return this.cdkVirtualForTrackBy ? this.cdkVirtualForTrackBy(index, item) : item;
|
|
});
|
|
}
|
|
this._needsUpdate = true;
|
|
}
|
|
/** Swap out one `DataSource` for another. */
|
|
_changeDataSource(oldDs, newDs) {
|
|
if (oldDs) {
|
|
oldDs.disconnect(this);
|
|
}
|
|
this._needsUpdate = true;
|
|
return newDs ? newDs.connect(this) : of();
|
|
}
|
|
/** Update the `CdkVirtualForOfContext` for all views. */
|
|
_updateContext() {
|
|
const count = this._data.length;
|
|
let i = this._viewContainerRef.length;
|
|
while (i--) {
|
|
const view = this._viewContainerRef.get(i);
|
|
view.context.index = this._renderedRange.start + i;
|
|
view.context.count = count;
|
|
this._updateComputedContextProperties(view.context);
|
|
view.detectChanges();
|
|
}
|
|
}
|
|
/** Apply changes to the DOM. */
|
|
_applyChanges(changes) {
|
|
this._viewRepeater.applyChanges(changes, this._viewContainerRef, (record, _adjustedPreviousIndex, currentIndex) => this._getEmbeddedViewArgs(record, currentIndex), (record) => record.item);
|
|
changes.forEachIdentityChange((record) => {
|
|
const view = this._viewContainerRef.get(record.currentIndex);
|
|
view.context.$implicit = record.item;
|
|
});
|
|
const count = this._data.length;
|
|
let i = this._viewContainerRef.length;
|
|
while (i--) {
|
|
const view = this._viewContainerRef.get(i);
|
|
view.context.index = this._renderedRange.start + i;
|
|
view.context.count = count;
|
|
this._updateComputedContextProperties(view.context);
|
|
}
|
|
}
|
|
/** Update the computed properties on the `CdkVirtualForOfContext`. */
|
|
_updateComputedContextProperties(context) {
|
|
context.first = context.index === 0;
|
|
context.last = context.index === context.count - 1;
|
|
context.even = context.index % 2 === 0;
|
|
context.odd = !context.even;
|
|
}
|
|
_getEmbeddedViewArgs(record, index) {
|
|
return {
|
|
templateRef: this._template,
|
|
context: {
|
|
$implicit: record.item,
|
|
// It's guaranteed that the iterable is not "undefined" or "null" because we only
|
|
// generate views for elements if the "cdkVirtualForOf" iterable has elements.
|
|
cdkVirtualForOf: this._cdkVirtualForOf,
|
|
index: -1,
|
|
count: -1,
|
|
first: false,
|
|
last: false,
|
|
odd: false,
|
|
even: false
|
|
},
|
|
index
|
|
};
|
|
}
|
|
};
|
|
_CdkVirtualForOf.ɵfac = function CdkVirtualForOf_Factory(t) {
|
|
return new (t || _CdkVirtualForOf)(ɵɵdirectiveInject(ViewContainerRef), ɵɵdirectiveInject(TemplateRef), ɵɵdirectiveInject(IterableDiffers), ɵɵdirectiveInject(_VIEW_REPEATER_STRATEGY), ɵɵdirectiveInject(CdkVirtualScrollViewport, 4), ɵɵdirectiveInject(NgZone));
|
|
};
|
|
_CdkVirtualForOf.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkVirtualForOf,
|
|
selectors: [["", "cdkVirtualFor", "", "cdkVirtualForOf", ""]],
|
|
inputs: {
|
|
cdkVirtualForOf: "cdkVirtualForOf",
|
|
cdkVirtualForTrackBy: "cdkVirtualForTrackBy",
|
|
cdkVirtualForTemplate: "cdkVirtualForTemplate",
|
|
cdkVirtualForTemplateCacheSize: "cdkVirtualForTemplateCacheSize"
|
|
},
|
|
standalone: true,
|
|
features: [ɵɵProvidersFeature([{
|
|
provide: _VIEW_REPEATER_STRATEGY,
|
|
useClass: _RecycleViewRepeaterStrategy
|
|
}])]
|
|
});
|
|
var CdkVirtualForOf = _CdkVirtualForOf;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkVirtualForOf, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkVirtualFor][cdkVirtualForOf]",
|
|
providers: [{
|
|
provide: _VIEW_REPEATER_STRATEGY,
|
|
useClass: _RecycleViewRepeaterStrategy
|
|
}],
|
|
standalone: true
|
|
}]
|
|
}], () => [{
|
|
type: ViewContainerRef
|
|
}, {
|
|
type: TemplateRef
|
|
}, {
|
|
type: IterableDiffers
|
|
}, {
|
|
type: _RecycleViewRepeaterStrategy,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [_VIEW_REPEATER_STRATEGY]
|
|
}]
|
|
}, {
|
|
type: CdkVirtualScrollViewport,
|
|
decorators: [{
|
|
type: SkipSelf
|
|
}]
|
|
}, {
|
|
type: NgZone
|
|
}], {
|
|
cdkVirtualForOf: [{
|
|
type: Input
|
|
}],
|
|
cdkVirtualForTrackBy: [{
|
|
type: Input
|
|
}],
|
|
cdkVirtualForTemplate: [{
|
|
type: Input
|
|
}],
|
|
cdkVirtualForTemplateCacheSize: [{
|
|
type: Input
|
|
}]
|
|
});
|
|
})();
|
|
var _CdkVirtualScrollableElement = class _CdkVirtualScrollableElement extends CdkVirtualScrollable {
|
|
constructor(elementRef, scrollDispatcher, ngZone, dir) {
|
|
super(elementRef, scrollDispatcher, ngZone, dir);
|
|
}
|
|
measureBoundingClientRectWithScrollOffset(from) {
|
|
return this.getElementRef().nativeElement.getBoundingClientRect()[from] - this.measureScrollOffset(from);
|
|
}
|
|
};
|
|
_CdkVirtualScrollableElement.ɵfac = function CdkVirtualScrollableElement_Factory(t) {
|
|
return new (t || _CdkVirtualScrollableElement)(ɵɵdirectiveInject(ElementRef), ɵɵdirectiveInject(ScrollDispatcher), ɵɵdirectiveInject(NgZone), ɵɵdirectiveInject(Directionality, 8));
|
|
};
|
|
_CdkVirtualScrollableElement.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkVirtualScrollableElement,
|
|
selectors: [["", "cdkVirtualScrollingElement", ""]],
|
|
hostAttrs: [1, "cdk-virtual-scrollable"],
|
|
standalone: true,
|
|
features: [ɵɵProvidersFeature([{
|
|
provide: VIRTUAL_SCROLLABLE,
|
|
useExisting: _CdkVirtualScrollableElement
|
|
}]), ɵɵInheritDefinitionFeature]
|
|
});
|
|
var CdkVirtualScrollableElement = _CdkVirtualScrollableElement;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkVirtualScrollableElement, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkVirtualScrollingElement]",
|
|
providers: [{
|
|
provide: VIRTUAL_SCROLLABLE,
|
|
useExisting: CdkVirtualScrollableElement
|
|
}],
|
|
standalone: true,
|
|
host: {
|
|
"class": "cdk-virtual-scrollable"
|
|
}
|
|
}]
|
|
}], () => [{
|
|
type: ElementRef
|
|
}, {
|
|
type: ScrollDispatcher
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: Directionality,
|
|
decorators: [{
|
|
type: Optional
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _CdkVirtualScrollableWindow = class _CdkVirtualScrollableWindow extends CdkVirtualScrollable {
|
|
constructor(scrollDispatcher, ngZone, dir) {
|
|
super(new ElementRef(document.documentElement), scrollDispatcher, ngZone, dir);
|
|
this._elementScrolled = new Observable((observer) => this.ngZone.runOutsideAngular(() => fromEvent(document, "scroll").pipe(takeUntil(this._destroyed)).subscribe(observer)));
|
|
}
|
|
measureBoundingClientRectWithScrollOffset(from) {
|
|
return this.getElementRef().nativeElement.getBoundingClientRect()[from];
|
|
}
|
|
};
|
|
_CdkVirtualScrollableWindow.ɵfac = function CdkVirtualScrollableWindow_Factory(t) {
|
|
return new (t || _CdkVirtualScrollableWindow)(ɵɵdirectiveInject(ScrollDispatcher), ɵɵdirectiveInject(NgZone), ɵɵdirectiveInject(Directionality, 8));
|
|
};
|
|
_CdkVirtualScrollableWindow.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkVirtualScrollableWindow,
|
|
selectors: [["cdk-virtual-scroll-viewport", "scrollWindow", ""]],
|
|
standalone: true,
|
|
features: [ɵɵProvidersFeature([{
|
|
provide: VIRTUAL_SCROLLABLE,
|
|
useExisting: _CdkVirtualScrollableWindow
|
|
}]), ɵɵInheritDefinitionFeature]
|
|
});
|
|
var CdkVirtualScrollableWindow = _CdkVirtualScrollableWindow;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkVirtualScrollableWindow, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "cdk-virtual-scroll-viewport[scrollWindow]",
|
|
providers: [{
|
|
provide: VIRTUAL_SCROLLABLE,
|
|
useExisting: CdkVirtualScrollableWindow
|
|
}],
|
|
standalone: true
|
|
}]
|
|
}], () => [{
|
|
type: ScrollDispatcher
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: Directionality,
|
|
decorators: [{
|
|
type: Optional
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _CdkScrollableModule = class _CdkScrollableModule {
|
|
};
|
|
_CdkScrollableModule.ɵfac = function CdkScrollableModule_Factory(t) {
|
|
return new (t || _CdkScrollableModule)();
|
|
};
|
|
_CdkScrollableModule.ɵmod = ɵɵdefineNgModule({
|
|
type: _CdkScrollableModule,
|
|
imports: [CdkScrollable],
|
|
exports: [CdkScrollable]
|
|
});
|
|
_CdkScrollableModule.ɵinj = ɵɵdefineInjector({});
|
|
var CdkScrollableModule = _CdkScrollableModule;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkScrollableModule, [{
|
|
type: NgModule,
|
|
args: [{
|
|
exports: [CdkScrollable],
|
|
imports: [CdkScrollable]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var _ScrollingModule = class _ScrollingModule {
|
|
};
|
|
_ScrollingModule.ɵfac = function ScrollingModule_Factory(t) {
|
|
return new (t || _ScrollingModule)();
|
|
};
|
|
_ScrollingModule.ɵmod = ɵɵdefineNgModule({
|
|
type: _ScrollingModule,
|
|
imports: [BidiModule, CdkScrollableModule, CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollableWindow, CdkVirtualScrollableElement],
|
|
exports: [BidiModule, CdkScrollableModule, CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, CdkVirtualScrollableWindow, CdkVirtualScrollableElement]
|
|
});
|
|
_ScrollingModule.ɵinj = ɵɵdefineInjector({
|
|
imports: [BidiModule, CdkScrollableModule, BidiModule, CdkScrollableModule]
|
|
});
|
|
var ScrollingModule = _ScrollingModule;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ScrollingModule, [{
|
|
type: NgModule,
|
|
args: [{
|
|
imports: [BidiModule, CdkScrollableModule, CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollableWindow, CdkVirtualScrollableElement],
|
|
exports: [BidiModule, CdkScrollableModule, CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, CdkVirtualScrollableWindow, CdkVirtualScrollableElement]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/portal.mjs
|
|
function throwNullPortalError() {
|
|
throw Error("Must provide a portal to attach");
|
|
}
|
|
function throwPortalAlreadyAttachedError() {
|
|
throw Error("Host already has a portal attached");
|
|
}
|
|
function throwPortalOutletAlreadyDisposedError() {
|
|
throw Error("This PortalOutlet has already been disposed");
|
|
}
|
|
function throwUnknownPortalTypeError() {
|
|
throw Error("Attempting to attach an unknown Portal type. BasePortalOutlet accepts either a ComponentPortal or a TemplatePortal.");
|
|
}
|
|
function throwNullPortalOutletError() {
|
|
throw Error("Attempting to attach a portal to a null PortalOutlet");
|
|
}
|
|
function throwNoPortalAttachedError() {
|
|
throw Error("Attempting to detach a portal that is not attached to a host");
|
|
}
|
|
var Portal = class {
|
|
/** Attach this portal to a host. */
|
|
attach(host) {
|
|
if (typeof ngDevMode === "undefined" || ngDevMode) {
|
|
if (host == null) {
|
|
throwNullPortalOutletError();
|
|
}
|
|
if (host.hasAttached()) {
|
|
throwPortalAlreadyAttachedError();
|
|
}
|
|
}
|
|
this._attachedHost = host;
|
|
return host.attach(this);
|
|
}
|
|
/** Detach this portal from its host */
|
|
detach() {
|
|
let host = this._attachedHost;
|
|
if (host != null) {
|
|
this._attachedHost = null;
|
|
host.detach();
|
|
} else if (typeof ngDevMode === "undefined" || ngDevMode) {
|
|
throwNoPortalAttachedError();
|
|
}
|
|
}
|
|
/** Whether this portal is attached to a host. */
|
|
get isAttached() {
|
|
return this._attachedHost != null;
|
|
}
|
|
/**
|
|
* Sets the PortalOutlet reference without performing `attach()`. This is used directly by
|
|
* the PortalOutlet when it is performing an `attach()` or `detach()`.
|
|
*/
|
|
setAttachedHost(host) {
|
|
this._attachedHost = host;
|
|
}
|
|
};
|
|
var ComponentPortal = class extends Portal {
|
|
constructor(component, viewContainerRef, injector, componentFactoryResolver, projectableNodes) {
|
|
super();
|
|
this.component = component;
|
|
this.viewContainerRef = viewContainerRef;
|
|
this.injector = injector;
|
|
this.componentFactoryResolver = componentFactoryResolver;
|
|
this.projectableNodes = projectableNodes;
|
|
}
|
|
};
|
|
var TemplatePortal = class extends Portal {
|
|
constructor(templateRef, viewContainerRef, context, injector) {
|
|
super();
|
|
this.templateRef = templateRef;
|
|
this.viewContainerRef = viewContainerRef;
|
|
this.context = context;
|
|
this.injector = injector;
|
|
}
|
|
get origin() {
|
|
return this.templateRef.elementRef;
|
|
}
|
|
/**
|
|
* Attach the portal to the provided `PortalOutlet`.
|
|
* When a context is provided it will override the `context` property of the `TemplatePortal`
|
|
* instance.
|
|
*/
|
|
attach(host, context = this.context) {
|
|
this.context = context;
|
|
return super.attach(host);
|
|
}
|
|
detach() {
|
|
this.context = void 0;
|
|
return super.detach();
|
|
}
|
|
};
|
|
var DomPortal = class extends Portal {
|
|
constructor(element) {
|
|
super();
|
|
this.element = element instanceof ElementRef ? element.nativeElement : element;
|
|
}
|
|
};
|
|
var BasePortalOutlet = class {
|
|
constructor() {
|
|
this._isDisposed = false;
|
|
this.attachDomPortal = null;
|
|
}
|
|
/** Whether this host has an attached portal. */
|
|
hasAttached() {
|
|
return !!this._attachedPortal;
|
|
}
|
|
/** Attaches a portal. */
|
|
attach(portal) {
|
|
if (typeof ngDevMode === "undefined" || ngDevMode) {
|
|
if (!portal) {
|
|
throwNullPortalError();
|
|
}
|
|
if (this.hasAttached()) {
|
|
throwPortalAlreadyAttachedError();
|
|
}
|
|
if (this._isDisposed) {
|
|
throwPortalOutletAlreadyDisposedError();
|
|
}
|
|
}
|
|
if (portal instanceof ComponentPortal) {
|
|
this._attachedPortal = portal;
|
|
return this.attachComponentPortal(portal);
|
|
} else if (portal instanceof TemplatePortal) {
|
|
this._attachedPortal = portal;
|
|
return this.attachTemplatePortal(portal);
|
|
} else if (this.attachDomPortal && portal instanceof DomPortal) {
|
|
this._attachedPortal = portal;
|
|
return this.attachDomPortal(portal);
|
|
}
|
|
if (typeof ngDevMode === "undefined" || ngDevMode) {
|
|
throwUnknownPortalTypeError();
|
|
}
|
|
}
|
|
/** Detaches a previously attached portal. */
|
|
detach() {
|
|
if (this._attachedPortal) {
|
|
this._attachedPortal.setAttachedHost(null);
|
|
this._attachedPortal = null;
|
|
}
|
|
this._invokeDisposeFn();
|
|
}
|
|
/** Permanently dispose of this portal host. */
|
|
dispose() {
|
|
if (this.hasAttached()) {
|
|
this.detach();
|
|
}
|
|
this._invokeDisposeFn();
|
|
this._isDisposed = true;
|
|
}
|
|
/** @docs-private */
|
|
setDisposeFn(fn) {
|
|
this._disposeFn = fn;
|
|
}
|
|
_invokeDisposeFn() {
|
|
if (this._disposeFn) {
|
|
this._disposeFn();
|
|
this._disposeFn = null;
|
|
}
|
|
}
|
|
};
|
|
var DomPortalOutlet = class extends BasePortalOutlet {
|
|
/**
|
|
* @param outletElement Element into which the content is projected.
|
|
* @param _componentFactoryResolver Used to resolve the component factory.
|
|
* Only required when attaching component portals.
|
|
* @param _appRef Reference to the application. Only used in component portals when there
|
|
* is no `ViewContainerRef` available.
|
|
* @param _defaultInjector Injector to use as a fallback when the portal being attached doesn't
|
|
* have one. Only used for component portals.
|
|
* @param _document Reference to the document. Used when attaching a DOM portal. Will eventually
|
|
* become a required parameter.
|
|
*/
|
|
constructor(outletElement, _componentFactoryResolver, _appRef, _defaultInjector, _document) {
|
|
super();
|
|
this.outletElement = outletElement;
|
|
this._componentFactoryResolver = _componentFactoryResolver;
|
|
this._appRef = _appRef;
|
|
this._defaultInjector = _defaultInjector;
|
|
this.attachDomPortal = (portal) => {
|
|
if (!this._document && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error("Cannot attach DOM portal without _document constructor parameter");
|
|
}
|
|
const element = portal.element;
|
|
if (!element.parentNode && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error("DOM portal content must be attached to a parent node.");
|
|
}
|
|
const anchorNode = this._document.createComment("dom-portal");
|
|
element.parentNode.insertBefore(anchorNode, element);
|
|
this.outletElement.appendChild(element);
|
|
this._attachedPortal = portal;
|
|
super.setDisposeFn(() => {
|
|
if (anchorNode.parentNode) {
|
|
anchorNode.parentNode.replaceChild(element, anchorNode);
|
|
}
|
|
});
|
|
};
|
|
this._document = _document;
|
|
}
|
|
/**
|
|
* Attach the given ComponentPortal to DOM element using the ComponentFactoryResolver.
|
|
* @param portal Portal to be attached
|
|
* @returns Reference to the created component.
|
|
*/
|
|
attachComponentPortal(portal) {
|
|
const resolver = portal.componentFactoryResolver || this._componentFactoryResolver;
|
|
if ((typeof ngDevMode === "undefined" || ngDevMode) && !resolver) {
|
|
throw Error("Cannot attach component portal to outlet without a ComponentFactoryResolver.");
|
|
}
|
|
const componentFactory = resolver.resolveComponentFactory(portal.component);
|
|
let componentRef;
|
|
if (portal.viewContainerRef) {
|
|
componentRef = portal.viewContainerRef.createComponent(componentFactory, portal.viewContainerRef.length, portal.injector || portal.viewContainerRef.injector, portal.projectableNodes || void 0);
|
|
this.setDisposeFn(() => componentRef.destroy());
|
|
} else {
|
|
if ((typeof ngDevMode === "undefined" || ngDevMode) && !this._appRef) {
|
|
throw Error("Cannot attach component portal to outlet without an ApplicationRef.");
|
|
}
|
|
componentRef = componentFactory.create(portal.injector || this._defaultInjector || Injector.NULL);
|
|
this._appRef.attachView(componentRef.hostView);
|
|
this.setDisposeFn(() => {
|
|
if (this._appRef.viewCount > 0) {
|
|
this._appRef.detachView(componentRef.hostView);
|
|
}
|
|
componentRef.destroy();
|
|
});
|
|
}
|
|
this.outletElement.appendChild(this._getComponentRootNode(componentRef));
|
|
this._attachedPortal = portal;
|
|
return componentRef;
|
|
}
|
|
/**
|
|
* Attaches a template portal to the DOM as an embedded view.
|
|
* @param portal Portal to be attached.
|
|
* @returns Reference to the created embedded view.
|
|
*/
|
|
attachTemplatePortal(portal) {
|
|
let viewContainer = portal.viewContainerRef;
|
|
let viewRef = viewContainer.createEmbeddedView(portal.templateRef, portal.context, {
|
|
injector: portal.injector
|
|
});
|
|
viewRef.rootNodes.forEach((rootNode) => this.outletElement.appendChild(rootNode));
|
|
viewRef.detectChanges();
|
|
this.setDisposeFn(() => {
|
|
let index = viewContainer.indexOf(viewRef);
|
|
if (index !== -1) {
|
|
viewContainer.remove(index);
|
|
}
|
|
});
|
|
this._attachedPortal = portal;
|
|
return viewRef;
|
|
}
|
|
/**
|
|
* Clears out a portal from the DOM.
|
|
*/
|
|
dispose() {
|
|
super.dispose();
|
|
this.outletElement.remove();
|
|
}
|
|
/** Gets the root HTMLElement for an instantiated component. */
|
|
_getComponentRootNode(componentRef) {
|
|
return componentRef.hostView.rootNodes[0];
|
|
}
|
|
};
|
|
var _CdkPortal = class _CdkPortal extends TemplatePortal {
|
|
constructor(templateRef, viewContainerRef) {
|
|
super(templateRef, viewContainerRef);
|
|
}
|
|
};
|
|
_CdkPortal.ɵfac = function CdkPortal_Factory(t) {
|
|
return new (t || _CdkPortal)(ɵɵdirectiveInject(TemplateRef), ɵɵdirectiveInject(ViewContainerRef));
|
|
};
|
|
_CdkPortal.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkPortal,
|
|
selectors: [["", "cdkPortal", ""]],
|
|
exportAs: ["cdkPortal"],
|
|
features: [ɵɵInheritDefinitionFeature]
|
|
});
|
|
var CdkPortal = _CdkPortal;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkPortal, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkPortal]",
|
|
exportAs: "cdkPortal"
|
|
}]
|
|
}], () => [{
|
|
type: TemplateRef
|
|
}, {
|
|
type: ViewContainerRef
|
|
}], null);
|
|
})();
|
|
var _TemplatePortalDirective = class _TemplatePortalDirective extends CdkPortal {
|
|
};
|
|
_TemplatePortalDirective.ɵfac = (() => {
|
|
let ɵTemplatePortalDirective_BaseFactory;
|
|
return function TemplatePortalDirective_Factory(t) {
|
|
return (ɵTemplatePortalDirective_BaseFactory || (ɵTemplatePortalDirective_BaseFactory = ɵɵgetInheritedFactory(_TemplatePortalDirective)))(t || _TemplatePortalDirective);
|
|
};
|
|
})();
|
|
_TemplatePortalDirective.ɵdir = ɵɵdefineDirective({
|
|
type: _TemplatePortalDirective,
|
|
selectors: [["", "cdk-portal", ""], ["", "portal", ""]],
|
|
exportAs: ["cdkPortal"],
|
|
features: [ɵɵProvidersFeature([{
|
|
provide: CdkPortal,
|
|
useExisting: _TemplatePortalDirective
|
|
}]), ɵɵInheritDefinitionFeature]
|
|
});
|
|
var TemplatePortalDirective = _TemplatePortalDirective;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(TemplatePortalDirective, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdk-portal], [portal]",
|
|
exportAs: "cdkPortal",
|
|
providers: [{
|
|
provide: CdkPortal,
|
|
useExisting: TemplatePortalDirective
|
|
}]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var _CdkPortalOutlet = class _CdkPortalOutlet extends BasePortalOutlet {
|
|
constructor(_componentFactoryResolver, _viewContainerRef, _document) {
|
|
super();
|
|
this._componentFactoryResolver = _componentFactoryResolver;
|
|
this._viewContainerRef = _viewContainerRef;
|
|
this._isInitialized = false;
|
|
this.attached = new EventEmitter();
|
|
this.attachDomPortal = (portal) => {
|
|
if (!this._document && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error("Cannot attach DOM portal without _document constructor parameter");
|
|
}
|
|
const element = portal.element;
|
|
if (!element.parentNode && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error("DOM portal content must be attached to a parent node.");
|
|
}
|
|
const anchorNode = this._document.createComment("dom-portal");
|
|
portal.setAttachedHost(this);
|
|
element.parentNode.insertBefore(anchorNode, element);
|
|
this._getRootNode().appendChild(element);
|
|
this._attachedPortal = portal;
|
|
super.setDisposeFn(() => {
|
|
if (anchorNode.parentNode) {
|
|
anchorNode.parentNode.replaceChild(element, anchorNode);
|
|
}
|
|
});
|
|
};
|
|
this._document = _document;
|
|
}
|
|
/** Portal associated with the Portal outlet. */
|
|
get portal() {
|
|
return this._attachedPortal;
|
|
}
|
|
set portal(portal) {
|
|
if (this.hasAttached() && !portal && !this._isInitialized) {
|
|
return;
|
|
}
|
|
if (this.hasAttached()) {
|
|
super.detach();
|
|
}
|
|
if (portal) {
|
|
super.attach(portal);
|
|
}
|
|
this._attachedPortal = portal || null;
|
|
}
|
|
/** Component or view reference that is attached to the portal. */
|
|
get attachedRef() {
|
|
return this._attachedRef;
|
|
}
|
|
ngOnInit() {
|
|
this._isInitialized = true;
|
|
}
|
|
ngOnDestroy() {
|
|
super.dispose();
|
|
this._attachedRef = this._attachedPortal = null;
|
|
}
|
|
/**
|
|
* Attach the given ComponentPortal to this PortalOutlet using the ComponentFactoryResolver.
|
|
*
|
|
* @param portal Portal to be attached to the portal outlet.
|
|
* @returns Reference to the created component.
|
|
*/
|
|
attachComponentPortal(portal) {
|
|
portal.setAttachedHost(this);
|
|
const viewContainerRef = portal.viewContainerRef != null ? portal.viewContainerRef : this._viewContainerRef;
|
|
const resolver = portal.componentFactoryResolver || this._componentFactoryResolver;
|
|
const componentFactory = resolver.resolveComponentFactory(portal.component);
|
|
const ref = viewContainerRef.createComponent(componentFactory, viewContainerRef.length, portal.injector || viewContainerRef.injector, portal.projectableNodes || void 0);
|
|
if (viewContainerRef !== this._viewContainerRef) {
|
|
this._getRootNode().appendChild(ref.hostView.rootNodes[0]);
|
|
}
|
|
super.setDisposeFn(() => ref.destroy());
|
|
this._attachedPortal = portal;
|
|
this._attachedRef = ref;
|
|
this.attached.emit(ref);
|
|
return ref;
|
|
}
|
|
/**
|
|
* Attach the given TemplatePortal to this PortalHost as an embedded View.
|
|
* @param portal Portal to be attached.
|
|
* @returns Reference to the created embedded view.
|
|
*/
|
|
attachTemplatePortal(portal) {
|
|
portal.setAttachedHost(this);
|
|
const viewRef = this._viewContainerRef.createEmbeddedView(portal.templateRef, portal.context, {
|
|
injector: portal.injector
|
|
});
|
|
super.setDisposeFn(() => this._viewContainerRef.clear());
|
|
this._attachedPortal = portal;
|
|
this._attachedRef = viewRef;
|
|
this.attached.emit(viewRef);
|
|
return viewRef;
|
|
}
|
|
/** Gets the root node of the portal outlet. */
|
|
_getRootNode() {
|
|
const nativeElement = this._viewContainerRef.element.nativeElement;
|
|
return nativeElement.nodeType === nativeElement.ELEMENT_NODE ? nativeElement : nativeElement.parentNode;
|
|
}
|
|
};
|
|
_CdkPortalOutlet.ɵfac = function CdkPortalOutlet_Factory(t) {
|
|
return new (t || _CdkPortalOutlet)(ɵɵdirectiveInject(ComponentFactoryResolver$1), ɵɵdirectiveInject(ViewContainerRef), ɵɵdirectiveInject(DOCUMENT));
|
|
};
|
|
_CdkPortalOutlet.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkPortalOutlet,
|
|
selectors: [["", "cdkPortalOutlet", ""]],
|
|
inputs: {
|
|
portal: ["cdkPortalOutlet", "portal"]
|
|
},
|
|
outputs: {
|
|
attached: "attached"
|
|
},
|
|
exportAs: ["cdkPortalOutlet"],
|
|
features: [ɵɵInheritDefinitionFeature]
|
|
});
|
|
var CdkPortalOutlet = _CdkPortalOutlet;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkPortalOutlet, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkPortalOutlet]",
|
|
exportAs: "cdkPortalOutlet",
|
|
inputs: ["portal: cdkPortalOutlet"]
|
|
}]
|
|
}], () => [{
|
|
type: ComponentFactoryResolver$1
|
|
}, {
|
|
type: ViewContainerRef
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}], {
|
|
attached: [{
|
|
type: Output
|
|
}]
|
|
});
|
|
})();
|
|
var _PortalHostDirective = class _PortalHostDirective extends CdkPortalOutlet {
|
|
};
|
|
_PortalHostDirective.ɵfac = (() => {
|
|
let ɵPortalHostDirective_BaseFactory;
|
|
return function PortalHostDirective_Factory(t) {
|
|
return (ɵPortalHostDirective_BaseFactory || (ɵPortalHostDirective_BaseFactory = ɵɵgetInheritedFactory(_PortalHostDirective)))(t || _PortalHostDirective);
|
|
};
|
|
})();
|
|
_PortalHostDirective.ɵdir = ɵɵdefineDirective({
|
|
type: _PortalHostDirective,
|
|
selectors: [["", "cdkPortalHost", ""], ["", "portalHost", ""]],
|
|
inputs: {
|
|
portal: ["cdkPortalHost", "portal"]
|
|
},
|
|
exportAs: ["cdkPortalHost"],
|
|
features: [ɵɵProvidersFeature([{
|
|
provide: CdkPortalOutlet,
|
|
useExisting: _PortalHostDirective
|
|
}]), ɵɵInheritDefinitionFeature]
|
|
});
|
|
var PortalHostDirective = _PortalHostDirective;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(PortalHostDirective, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdkPortalHost], [portalHost]",
|
|
exportAs: "cdkPortalHost",
|
|
inputs: ["portal: cdkPortalHost"],
|
|
providers: [{
|
|
provide: CdkPortalOutlet,
|
|
useExisting: PortalHostDirective
|
|
}]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var _PortalModule = class _PortalModule {
|
|
};
|
|
_PortalModule.ɵfac = function PortalModule_Factory(t) {
|
|
return new (t || _PortalModule)();
|
|
};
|
|
_PortalModule.ɵmod = ɵɵdefineNgModule({
|
|
type: _PortalModule,
|
|
declarations: [CdkPortal, CdkPortalOutlet, TemplatePortalDirective, PortalHostDirective],
|
|
exports: [CdkPortal, CdkPortalOutlet, TemplatePortalDirective, PortalHostDirective]
|
|
});
|
|
_PortalModule.ɵinj = ɵɵdefineInjector({});
|
|
var PortalModule = _PortalModule;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(PortalModule, [{
|
|
type: NgModule,
|
|
args: [{
|
|
exports: [CdkPortal, CdkPortalOutlet, TemplatePortalDirective, PortalHostDirective],
|
|
declarations: [CdkPortal, CdkPortalOutlet, TemplatePortalDirective, PortalHostDirective]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
|
|
// node_modules/@angular/cdk/fesm2022/overlay.mjs
|
|
var scrollBehaviorSupported = supportsScrollBehavior();
|
|
var BlockScrollStrategy = class {
|
|
constructor(_viewportRuler, document2) {
|
|
this._viewportRuler = _viewportRuler;
|
|
this._previousHTMLStyles = {
|
|
top: "",
|
|
left: ""
|
|
};
|
|
this._isEnabled = false;
|
|
this._document = document2;
|
|
}
|
|
/** Attaches this scroll strategy to an overlay. */
|
|
attach() {
|
|
}
|
|
/** Blocks page-level scroll while the attached overlay is open. */
|
|
enable() {
|
|
if (this._canBeEnabled()) {
|
|
const root = this._document.documentElement;
|
|
this._previousScrollPosition = this._viewportRuler.getViewportScrollPosition();
|
|
this._previousHTMLStyles.left = root.style.left || "";
|
|
this._previousHTMLStyles.top = root.style.top || "";
|
|
root.style.left = coerceCssPixelValue(-this._previousScrollPosition.left);
|
|
root.style.top = coerceCssPixelValue(-this._previousScrollPosition.top);
|
|
root.classList.add("cdk-global-scrollblock");
|
|
this._isEnabled = true;
|
|
}
|
|
}
|
|
/** Unblocks page-level scroll while the attached overlay is open. */
|
|
disable() {
|
|
if (this._isEnabled) {
|
|
const html = this._document.documentElement;
|
|
const body = this._document.body;
|
|
const htmlStyle = html.style;
|
|
const bodyStyle = body.style;
|
|
const previousHtmlScrollBehavior = htmlStyle.scrollBehavior || "";
|
|
const previousBodyScrollBehavior = bodyStyle.scrollBehavior || "";
|
|
this._isEnabled = false;
|
|
htmlStyle.left = this._previousHTMLStyles.left;
|
|
htmlStyle.top = this._previousHTMLStyles.top;
|
|
html.classList.remove("cdk-global-scrollblock");
|
|
if (scrollBehaviorSupported) {
|
|
htmlStyle.scrollBehavior = bodyStyle.scrollBehavior = "auto";
|
|
}
|
|
window.scroll(this._previousScrollPosition.left, this._previousScrollPosition.top);
|
|
if (scrollBehaviorSupported) {
|
|
htmlStyle.scrollBehavior = previousHtmlScrollBehavior;
|
|
bodyStyle.scrollBehavior = previousBodyScrollBehavior;
|
|
}
|
|
}
|
|
}
|
|
_canBeEnabled() {
|
|
const html = this._document.documentElement;
|
|
if (html.classList.contains("cdk-global-scrollblock") || this._isEnabled) {
|
|
return false;
|
|
}
|
|
const body = this._document.body;
|
|
const viewport = this._viewportRuler.getViewportSize();
|
|
return body.scrollHeight > viewport.height || body.scrollWidth > viewport.width;
|
|
}
|
|
};
|
|
function getMatScrollStrategyAlreadyAttachedError() {
|
|
return Error(`Scroll strategy has already been attached.`);
|
|
}
|
|
var CloseScrollStrategy = class {
|
|
constructor(_scrollDispatcher, _ngZone, _viewportRuler, _config) {
|
|
this._scrollDispatcher = _scrollDispatcher;
|
|
this._ngZone = _ngZone;
|
|
this._viewportRuler = _viewportRuler;
|
|
this._config = _config;
|
|
this._scrollSubscription = null;
|
|
this._detach = () => {
|
|
this.disable();
|
|
if (this._overlayRef.hasAttached()) {
|
|
this._ngZone.run(() => this._overlayRef.detach());
|
|
}
|
|
};
|
|
}
|
|
/** Attaches this scroll strategy to an overlay. */
|
|
attach(overlayRef) {
|
|
if (this._overlayRef && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw getMatScrollStrategyAlreadyAttachedError();
|
|
}
|
|
this._overlayRef = overlayRef;
|
|
}
|
|
/** Enables the closing of the attached overlay on scroll. */
|
|
enable() {
|
|
if (this._scrollSubscription) {
|
|
return;
|
|
}
|
|
const stream = this._scrollDispatcher.scrolled(0).pipe(filter((scrollable) => {
|
|
return !scrollable || !this._overlayRef.overlayElement.contains(scrollable.getElementRef().nativeElement);
|
|
}));
|
|
if (this._config && this._config.threshold && this._config.threshold > 1) {
|
|
this._initialScrollPosition = this._viewportRuler.getViewportScrollPosition().top;
|
|
this._scrollSubscription = stream.subscribe(() => {
|
|
const scrollPosition = this._viewportRuler.getViewportScrollPosition().top;
|
|
if (Math.abs(scrollPosition - this._initialScrollPosition) > this._config.threshold) {
|
|
this._detach();
|
|
} else {
|
|
this._overlayRef.updatePosition();
|
|
}
|
|
});
|
|
} else {
|
|
this._scrollSubscription = stream.subscribe(this._detach);
|
|
}
|
|
}
|
|
/** Disables the closing the attached overlay on scroll. */
|
|
disable() {
|
|
if (this._scrollSubscription) {
|
|
this._scrollSubscription.unsubscribe();
|
|
this._scrollSubscription = null;
|
|
}
|
|
}
|
|
detach() {
|
|
this.disable();
|
|
this._overlayRef = null;
|
|
}
|
|
};
|
|
var NoopScrollStrategy = class {
|
|
/** Does nothing, as this scroll strategy is a no-op. */
|
|
enable() {
|
|
}
|
|
/** Does nothing, as this scroll strategy is a no-op. */
|
|
disable() {
|
|
}
|
|
/** Does nothing, as this scroll strategy is a no-op. */
|
|
attach() {
|
|
}
|
|
};
|
|
function isElementScrolledOutsideView(element, scrollContainers) {
|
|
return scrollContainers.some((containerBounds) => {
|
|
const outsideAbove = element.bottom < containerBounds.top;
|
|
const outsideBelow = element.top > containerBounds.bottom;
|
|
const outsideLeft = element.right < containerBounds.left;
|
|
const outsideRight = element.left > containerBounds.right;
|
|
return outsideAbove || outsideBelow || outsideLeft || outsideRight;
|
|
});
|
|
}
|
|
function isElementClippedByScrolling(element, scrollContainers) {
|
|
return scrollContainers.some((scrollContainerRect) => {
|
|
const clippedAbove = element.top < scrollContainerRect.top;
|
|
const clippedBelow = element.bottom > scrollContainerRect.bottom;
|
|
const clippedLeft = element.left < scrollContainerRect.left;
|
|
const clippedRight = element.right > scrollContainerRect.right;
|
|
return clippedAbove || clippedBelow || clippedLeft || clippedRight;
|
|
});
|
|
}
|
|
var RepositionScrollStrategy = class {
|
|
constructor(_scrollDispatcher, _viewportRuler, _ngZone, _config) {
|
|
this._scrollDispatcher = _scrollDispatcher;
|
|
this._viewportRuler = _viewportRuler;
|
|
this._ngZone = _ngZone;
|
|
this._config = _config;
|
|
this._scrollSubscription = null;
|
|
}
|
|
/** Attaches this scroll strategy to an overlay. */
|
|
attach(overlayRef) {
|
|
if (this._overlayRef && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw getMatScrollStrategyAlreadyAttachedError();
|
|
}
|
|
this._overlayRef = overlayRef;
|
|
}
|
|
/** Enables repositioning of the attached overlay on scroll. */
|
|
enable() {
|
|
if (!this._scrollSubscription) {
|
|
const throttle = this._config ? this._config.scrollThrottle : 0;
|
|
this._scrollSubscription = this._scrollDispatcher.scrolled(throttle).subscribe(() => {
|
|
this._overlayRef.updatePosition();
|
|
if (this._config && this._config.autoClose) {
|
|
const overlayRect = this._overlayRef.overlayElement.getBoundingClientRect();
|
|
const {
|
|
width,
|
|
height
|
|
} = this._viewportRuler.getViewportSize();
|
|
const parentRects = [{
|
|
width,
|
|
height,
|
|
bottom: height,
|
|
right: width,
|
|
top: 0,
|
|
left: 0
|
|
}];
|
|
if (isElementScrolledOutsideView(overlayRect, parentRects)) {
|
|
this.disable();
|
|
this._ngZone.run(() => this._overlayRef.detach());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
/** Disables repositioning of the attached overlay on scroll. */
|
|
disable() {
|
|
if (this._scrollSubscription) {
|
|
this._scrollSubscription.unsubscribe();
|
|
this._scrollSubscription = null;
|
|
}
|
|
}
|
|
detach() {
|
|
this.disable();
|
|
this._overlayRef = null;
|
|
}
|
|
};
|
|
var _ScrollStrategyOptions = class _ScrollStrategyOptions {
|
|
constructor(_scrollDispatcher, _viewportRuler, _ngZone, document2) {
|
|
this._scrollDispatcher = _scrollDispatcher;
|
|
this._viewportRuler = _viewportRuler;
|
|
this._ngZone = _ngZone;
|
|
this.noop = () => new NoopScrollStrategy();
|
|
this.close = (config) => new CloseScrollStrategy(this._scrollDispatcher, this._ngZone, this._viewportRuler, config);
|
|
this.block = () => new BlockScrollStrategy(this._viewportRuler, this._document);
|
|
this.reposition = (config) => new RepositionScrollStrategy(this._scrollDispatcher, this._viewportRuler, this._ngZone, config);
|
|
this._document = document2;
|
|
}
|
|
};
|
|
_ScrollStrategyOptions.ɵfac = function ScrollStrategyOptions_Factory(t) {
|
|
return new (t || _ScrollStrategyOptions)(ɵɵinject(ScrollDispatcher), ɵɵinject(ViewportRuler), ɵɵinject(NgZone), ɵɵinject(DOCUMENT));
|
|
};
|
|
_ScrollStrategyOptions.ɵprov = ɵɵdefineInjectable({
|
|
token: _ScrollStrategyOptions,
|
|
factory: _ScrollStrategyOptions.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var ScrollStrategyOptions = _ScrollStrategyOptions;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(ScrollStrategyOptions, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: ScrollDispatcher
|
|
}, {
|
|
type: ViewportRuler
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var OverlayConfig = class {
|
|
constructor(config) {
|
|
this.scrollStrategy = new NoopScrollStrategy();
|
|
this.panelClass = "";
|
|
this.hasBackdrop = false;
|
|
this.backdropClass = "cdk-overlay-dark-backdrop";
|
|
this.disposeOnNavigation = false;
|
|
if (config) {
|
|
const configKeys = Object.keys(config);
|
|
for (const key of configKeys) {
|
|
if (config[key] !== void 0) {
|
|
this[key] = config[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
var ConnectionPositionPair = class {
|
|
constructor(origin, overlay, offsetX, offsetY, panelClass) {
|
|
this.offsetX = offsetX;
|
|
this.offsetY = offsetY;
|
|
this.panelClass = panelClass;
|
|
this.originX = origin.originX;
|
|
this.originY = origin.originY;
|
|
this.overlayX = overlay.overlayX;
|
|
this.overlayY = overlay.overlayY;
|
|
}
|
|
};
|
|
var ConnectedOverlayPositionChange = class {
|
|
constructor(connectionPair, scrollableViewProperties) {
|
|
this.connectionPair = connectionPair;
|
|
this.scrollableViewProperties = scrollableViewProperties;
|
|
}
|
|
};
|
|
function validateVerticalPosition(property, value) {
|
|
if (value !== "top" && value !== "bottom" && value !== "center") {
|
|
throw Error(`ConnectedPosition: Invalid ${property} "${value}". Expected "top", "bottom" or "center".`);
|
|
}
|
|
}
|
|
function validateHorizontalPosition(property, value) {
|
|
if (value !== "start" && value !== "end" && value !== "center") {
|
|
throw Error(`ConnectedPosition: Invalid ${property} "${value}". Expected "start", "end" or "center".`);
|
|
}
|
|
}
|
|
var _BaseOverlayDispatcher = class _BaseOverlayDispatcher {
|
|
constructor(document2) {
|
|
this._attachedOverlays = [];
|
|
this._document = document2;
|
|
}
|
|
ngOnDestroy() {
|
|
this.detach();
|
|
}
|
|
/** Add a new overlay to the list of attached overlay refs. */
|
|
add(overlayRef) {
|
|
this.remove(overlayRef);
|
|
this._attachedOverlays.push(overlayRef);
|
|
}
|
|
/** Remove an overlay from the list of attached overlay refs. */
|
|
remove(overlayRef) {
|
|
const index = this._attachedOverlays.indexOf(overlayRef);
|
|
if (index > -1) {
|
|
this._attachedOverlays.splice(index, 1);
|
|
}
|
|
if (this._attachedOverlays.length === 0) {
|
|
this.detach();
|
|
}
|
|
}
|
|
};
|
|
_BaseOverlayDispatcher.ɵfac = function BaseOverlayDispatcher_Factory(t) {
|
|
return new (t || _BaseOverlayDispatcher)(ɵɵinject(DOCUMENT));
|
|
};
|
|
_BaseOverlayDispatcher.ɵprov = ɵɵdefineInjectable({
|
|
token: _BaseOverlayDispatcher,
|
|
factory: _BaseOverlayDispatcher.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var BaseOverlayDispatcher = _BaseOverlayDispatcher;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(BaseOverlayDispatcher, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _OverlayKeyboardDispatcher = class _OverlayKeyboardDispatcher extends BaseOverlayDispatcher {
|
|
constructor(document2, _ngZone) {
|
|
super(document2);
|
|
this._ngZone = _ngZone;
|
|
this._keydownListener = (event) => {
|
|
const overlays = this._attachedOverlays;
|
|
for (let i = overlays.length - 1; i > -1; i--) {
|
|
if (overlays[i]._keydownEvents.observers.length > 0) {
|
|
const keydownEvents = overlays[i]._keydownEvents;
|
|
if (this._ngZone) {
|
|
this._ngZone.run(() => keydownEvents.next(event));
|
|
} else {
|
|
keydownEvents.next(event);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
/** Add a new overlay to the list of attached overlay refs. */
|
|
add(overlayRef) {
|
|
super.add(overlayRef);
|
|
if (!this._isAttached) {
|
|
if (this._ngZone) {
|
|
this._ngZone.runOutsideAngular(() => this._document.body.addEventListener("keydown", this._keydownListener));
|
|
} else {
|
|
this._document.body.addEventListener("keydown", this._keydownListener);
|
|
}
|
|
this._isAttached = true;
|
|
}
|
|
}
|
|
/** Detaches the global keyboard event listener. */
|
|
detach() {
|
|
if (this._isAttached) {
|
|
this._document.body.removeEventListener("keydown", this._keydownListener);
|
|
this._isAttached = false;
|
|
}
|
|
}
|
|
};
|
|
_OverlayKeyboardDispatcher.ɵfac = function OverlayKeyboardDispatcher_Factory(t) {
|
|
return new (t || _OverlayKeyboardDispatcher)(ɵɵinject(DOCUMENT), ɵɵinject(NgZone, 8));
|
|
};
|
|
_OverlayKeyboardDispatcher.ɵprov = ɵɵdefineInjectable({
|
|
token: _OverlayKeyboardDispatcher,
|
|
factory: _OverlayKeyboardDispatcher.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var OverlayKeyboardDispatcher = _OverlayKeyboardDispatcher;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(OverlayKeyboardDispatcher, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: NgZone,
|
|
decorators: [{
|
|
type: Optional
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _OverlayOutsideClickDispatcher = class _OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
|
|
constructor(document2, _platform, _ngZone) {
|
|
super(document2);
|
|
this._platform = _platform;
|
|
this._ngZone = _ngZone;
|
|
this._cursorStyleIsSet = false;
|
|
this._pointerDownListener = (event) => {
|
|
this._pointerDownEventTarget = _getEventTarget(event);
|
|
};
|
|
this._clickListener = (event) => {
|
|
const target = _getEventTarget(event);
|
|
const origin = event.type === "click" && this._pointerDownEventTarget ? this._pointerDownEventTarget : target;
|
|
this._pointerDownEventTarget = null;
|
|
const overlays = this._attachedOverlays.slice();
|
|
for (let i = overlays.length - 1; i > -1; i--) {
|
|
const overlayRef = overlays[i];
|
|
if (overlayRef._outsidePointerEvents.observers.length < 1 || !overlayRef.hasAttached()) {
|
|
continue;
|
|
}
|
|
if (overlayRef.overlayElement.contains(target) || overlayRef.overlayElement.contains(origin)) {
|
|
break;
|
|
}
|
|
const outsidePointerEvents = overlayRef._outsidePointerEvents;
|
|
if (this._ngZone) {
|
|
this._ngZone.run(() => outsidePointerEvents.next(event));
|
|
} else {
|
|
outsidePointerEvents.next(event);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
/** Add a new overlay to the list of attached overlay refs. */
|
|
add(overlayRef) {
|
|
super.add(overlayRef);
|
|
if (!this._isAttached) {
|
|
const body = this._document.body;
|
|
if (this._ngZone) {
|
|
this._ngZone.runOutsideAngular(() => this._addEventListeners(body));
|
|
} else {
|
|
this._addEventListeners(body);
|
|
}
|
|
if (this._platform.IOS && !this._cursorStyleIsSet) {
|
|
this._cursorOriginalValue = body.style.cursor;
|
|
body.style.cursor = "pointer";
|
|
this._cursorStyleIsSet = true;
|
|
}
|
|
this._isAttached = true;
|
|
}
|
|
}
|
|
/** Detaches the global keyboard event listener. */
|
|
detach() {
|
|
if (this._isAttached) {
|
|
const body = this._document.body;
|
|
body.removeEventListener("pointerdown", this._pointerDownListener, true);
|
|
body.removeEventListener("click", this._clickListener, true);
|
|
body.removeEventListener("auxclick", this._clickListener, true);
|
|
body.removeEventListener("contextmenu", this._clickListener, true);
|
|
if (this._platform.IOS && this._cursorStyleIsSet) {
|
|
body.style.cursor = this._cursorOriginalValue;
|
|
this._cursorStyleIsSet = false;
|
|
}
|
|
this._isAttached = false;
|
|
}
|
|
}
|
|
_addEventListeners(body) {
|
|
body.addEventListener("pointerdown", this._pointerDownListener, true);
|
|
body.addEventListener("click", this._clickListener, true);
|
|
body.addEventListener("auxclick", this._clickListener, true);
|
|
body.addEventListener("contextmenu", this._clickListener, true);
|
|
}
|
|
};
|
|
_OverlayOutsideClickDispatcher.ɵfac = function OverlayOutsideClickDispatcher_Factory(t) {
|
|
return new (t || _OverlayOutsideClickDispatcher)(ɵɵinject(DOCUMENT), ɵɵinject(Platform), ɵɵinject(NgZone, 8));
|
|
};
|
|
_OverlayOutsideClickDispatcher.ɵprov = ɵɵdefineInjectable({
|
|
token: _OverlayOutsideClickDispatcher,
|
|
factory: _OverlayOutsideClickDispatcher.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var OverlayOutsideClickDispatcher = _OverlayOutsideClickDispatcher;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(OverlayOutsideClickDispatcher, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: Platform
|
|
}, {
|
|
type: NgZone,
|
|
decorators: [{
|
|
type: Optional
|
|
}]
|
|
}], null);
|
|
})();
|
|
var _OverlayContainer = class _OverlayContainer {
|
|
constructor(document2, _platform) {
|
|
this._platform = _platform;
|
|
this._document = document2;
|
|
}
|
|
ngOnDestroy() {
|
|
this._containerElement?.remove();
|
|
}
|
|
/**
|
|
* This method returns the overlay container element. It will lazily
|
|
* create the element the first time it is called to facilitate using
|
|
* the container in non-browser environments.
|
|
* @returns the container element
|
|
*/
|
|
getContainerElement() {
|
|
if (!this._containerElement) {
|
|
this._createContainer();
|
|
}
|
|
return this._containerElement;
|
|
}
|
|
/**
|
|
* Create the overlay container element, which is simply a div
|
|
* with the 'cdk-overlay-container' class on the document body.
|
|
*/
|
|
_createContainer() {
|
|
const containerClass = "cdk-overlay-container";
|
|
if (this._platform.isBrowser || _isTestEnvironment()) {
|
|
const oppositePlatformContainers = this._document.querySelectorAll(`.${containerClass}[platform="server"], .${containerClass}[platform="test"]`);
|
|
for (let i = 0; i < oppositePlatformContainers.length; i++) {
|
|
oppositePlatformContainers[i].remove();
|
|
}
|
|
}
|
|
const container = this._document.createElement("div");
|
|
container.classList.add(containerClass);
|
|
if (_isTestEnvironment()) {
|
|
container.setAttribute("platform", "test");
|
|
} else if (!this._platform.isBrowser) {
|
|
container.setAttribute("platform", "server");
|
|
}
|
|
this._document.body.appendChild(container);
|
|
this._containerElement = container;
|
|
}
|
|
};
|
|
_OverlayContainer.ɵfac = function OverlayContainer_Factory(t) {
|
|
return new (t || _OverlayContainer)(ɵɵinject(DOCUMENT), ɵɵinject(Platform));
|
|
};
|
|
_OverlayContainer.ɵprov = ɵɵdefineInjectable({
|
|
token: _OverlayContainer,
|
|
factory: _OverlayContainer.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var OverlayContainer = _OverlayContainer;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(OverlayContainer, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: Platform
|
|
}], null);
|
|
})();
|
|
var OverlayRef = class {
|
|
constructor(_portalOutlet, _host, _pane, _config, _ngZone, _keyboardDispatcher, _document, _location, _outsideClickDispatcher, _animationsDisabled = false) {
|
|
this._portalOutlet = _portalOutlet;
|
|
this._host = _host;
|
|
this._pane = _pane;
|
|
this._config = _config;
|
|
this._ngZone = _ngZone;
|
|
this._keyboardDispatcher = _keyboardDispatcher;
|
|
this._document = _document;
|
|
this._location = _location;
|
|
this._outsideClickDispatcher = _outsideClickDispatcher;
|
|
this._animationsDisabled = _animationsDisabled;
|
|
this._backdropElement = null;
|
|
this._backdropClick = new Subject();
|
|
this._attachments = new Subject();
|
|
this._detachments = new Subject();
|
|
this._locationChanges = Subscription.EMPTY;
|
|
this._backdropClickHandler = (event) => this._backdropClick.next(event);
|
|
this._backdropTransitionendHandler = (event) => {
|
|
this._disposeBackdrop(event.target);
|
|
};
|
|
this._keydownEvents = new Subject();
|
|
this._outsidePointerEvents = new Subject();
|
|
if (_config.scrollStrategy) {
|
|
this._scrollStrategy = _config.scrollStrategy;
|
|
this._scrollStrategy.attach(this);
|
|
}
|
|
this._positionStrategy = _config.positionStrategy;
|
|
}
|
|
/** The overlay's HTML element */
|
|
get overlayElement() {
|
|
return this._pane;
|
|
}
|
|
/** The overlay's backdrop HTML element. */
|
|
get backdropElement() {
|
|
return this._backdropElement;
|
|
}
|
|
/**
|
|
* Wrapper around the panel element. Can be used for advanced
|
|
* positioning where a wrapper with specific styling is
|
|
* required around the overlay pane.
|
|
*/
|
|
get hostElement() {
|
|
return this._host;
|
|
}
|
|
/**
|
|
* Attaches content, given via a Portal, to the overlay.
|
|
* If the overlay is configured to have a backdrop, it will be created.
|
|
*
|
|
* @param portal Portal instance to which to attach the overlay.
|
|
* @returns The portal attachment result.
|
|
*/
|
|
attach(portal) {
|
|
if (!this._host.parentElement && this._previousHostParent) {
|
|
this._previousHostParent.appendChild(this._host);
|
|
}
|
|
const attachResult = this._portalOutlet.attach(portal);
|
|
if (this._positionStrategy) {
|
|
this._positionStrategy.attach(this);
|
|
}
|
|
this._updateStackingOrder();
|
|
this._updateElementSize();
|
|
this._updateElementDirection();
|
|
if (this._scrollStrategy) {
|
|
this._scrollStrategy.enable();
|
|
}
|
|
this._ngZone.onStable.pipe(take(1)).subscribe(() => {
|
|
if (this.hasAttached()) {
|
|
this.updatePosition();
|
|
}
|
|
});
|
|
this._togglePointerEvents(true);
|
|
if (this._config.hasBackdrop) {
|
|
this._attachBackdrop();
|
|
}
|
|
if (this._config.panelClass) {
|
|
this._toggleClasses(this._pane, this._config.panelClass, true);
|
|
}
|
|
this._attachments.next();
|
|
this._keyboardDispatcher.add(this);
|
|
if (this._config.disposeOnNavigation) {
|
|
this._locationChanges = this._location.subscribe(() => this.dispose());
|
|
}
|
|
this._outsideClickDispatcher.add(this);
|
|
if (typeof attachResult?.onDestroy === "function") {
|
|
attachResult.onDestroy(() => {
|
|
if (this.hasAttached()) {
|
|
this._ngZone.runOutsideAngular(() => Promise.resolve().then(() => this.detach()));
|
|
}
|
|
});
|
|
}
|
|
return attachResult;
|
|
}
|
|
/**
|
|
* Detaches an overlay from a portal.
|
|
* @returns The portal detachment result.
|
|
*/
|
|
detach() {
|
|
if (!this.hasAttached()) {
|
|
return;
|
|
}
|
|
this.detachBackdrop();
|
|
this._togglePointerEvents(false);
|
|
if (this._positionStrategy && this._positionStrategy.detach) {
|
|
this._positionStrategy.detach();
|
|
}
|
|
if (this._scrollStrategy) {
|
|
this._scrollStrategy.disable();
|
|
}
|
|
const detachmentResult = this._portalOutlet.detach();
|
|
this._detachments.next();
|
|
this._keyboardDispatcher.remove(this);
|
|
this._detachContentWhenStable();
|
|
this._locationChanges.unsubscribe();
|
|
this._outsideClickDispatcher.remove(this);
|
|
return detachmentResult;
|
|
}
|
|
/** Cleans up the overlay from the DOM. */
|
|
dispose() {
|
|
const isAttached = this.hasAttached();
|
|
if (this._positionStrategy) {
|
|
this._positionStrategy.dispose();
|
|
}
|
|
this._disposeScrollStrategy();
|
|
this._disposeBackdrop(this._backdropElement);
|
|
this._locationChanges.unsubscribe();
|
|
this._keyboardDispatcher.remove(this);
|
|
this._portalOutlet.dispose();
|
|
this._attachments.complete();
|
|
this._backdropClick.complete();
|
|
this._keydownEvents.complete();
|
|
this._outsidePointerEvents.complete();
|
|
this._outsideClickDispatcher.remove(this);
|
|
this._host?.remove();
|
|
this._previousHostParent = this._pane = this._host = null;
|
|
if (isAttached) {
|
|
this._detachments.next();
|
|
}
|
|
this._detachments.complete();
|
|
}
|
|
/** Whether the overlay has attached content. */
|
|
hasAttached() {
|
|
return this._portalOutlet.hasAttached();
|
|
}
|
|
/** Gets an observable that emits when the backdrop has been clicked. */
|
|
backdropClick() {
|
|
return this._backdropClick;
|
|
}
|
|
/** Gets an observable that emits when the overlay has been attached. */
|
|
attachments() {
|
|
return this._attachments;
|
|
}
|
|
/** Gets an observable that emits when the overlay has been detached. */
|
|
detachments() {
|
|
return this._detachments;
|
|
}
|
|
/** Gets an observable of keydown events targeted to this overlay. */
|
|
keydownEvents() {
|
|
return this._keydownEvents;
|
|
}
|
|
/** Gets an observable of pointer events targeted outside this overlay. */
|
|
outsidePointerEvents() {
|
|
return this._outsidePointerEvents;
|
|
}
|
|
/** Gets the current overlay configuration, which is immutable. */
|
|
getConfig() {
|
|
return this._config;
|
|
}
|
|
/** Updates the position of the overlay based on the position strategy. */
|
|
updatePosition() {
|
|
if (this._positionStrategy) {
|
|
this._positionStrategy.apply();
|
|
}
|
|
}
|
|
/** Switches to a new position strategy and updates the overlay position. */
|
|
updatePositionStrategy(strategy) {
|
|
if (strategy === this._positionStrategy) {
|
|
return;
|
|
}
|
|
if (this._positionStrategy) {
|
|
this._positionStrategy.dispose();
|
|
}
|
|
this._positionStrategy = strategy;
|
|
if (this.hasAttached()) {
|
|
strategy.attach(this);
|
|
this.updatePosition();
|
|
}
|
|
}
|
|
/** Update the size properties of the overlay. */
|
|
updateSize(sizeConfig) {
|
|
this._config = __spreadValues(__spreadValues({}, this._config), sizeConfig);
|
|
this._updateElementSize();
|
|
}
|
|
/** Sets the LTR/RTL direction for the overlay. */
|
|
setDirection(dir) {
|
|
this._config = __spreadProps(__spreadValues({}, this._config), {
|
|
direction: dir
|
|
});
|
|
this._updateElementDirection();
|
|
}
|
|
/** Add a CSS class or an array of classes to the overlay pane. */
|
|
addPanelClass(classes) {
|
|
if (this._pane) {
|
|
this._toggleClasses(this._pane, classes, true);
|
|
}
|
|
}
|
|
/** Remove a CSS class or an array of classes from the overlay pane. */
|
|
removePanelClass(classes) {
|
|
if (this._pane) {
|
|
this._toggleClasses(this._pane, classes, false);
|
|
}
|
|
}
|
|
/**
|
|
* Returns the layout direction of the overlay panel.
|
|
*/
|
|
getDirection() {
|
|
const direction = this._config.direction;
|
|
if (!direction) {
|
|
return "ltr";
|
|
}
|
|
return typeof direction === "string" ? direction : direction.value;
|
|
}
|
|
/** Switches to a new scroll strategy. */
|
|
updateScrollStrategy(strategy) {
|
|
if (strategy === this._scrollStrategy) {
|
|
return;
|
|
}
|
|
this._disposeScrollStrategy();
|
|
this._scrollStrategy = strategy;
|
|
if (this.hasAttached()) {
|
|
strategy.attach(this);
|
|
strategy.enable();
|
|
}
|
|
}
|
|
/** Updates the text direction of the overlay panel. */
|
|
_updateElementDirection() {
|
|
this._host.setAttribute("dir", this.getDirection());
|
|
}
|
|
/** Updates the size of the overlay element based on the overlay config. */
|
|
_updateElementSize() {
|
|
if (!this._pane) {
|
|
return;
|
|
}
|
|
const style = this._pane.style;
|
|
style.width = coerceCssPixelValue(this._config.width);
|
|
style.height = coerceCssPixelValue(this._config.height);
|
|
style.minWidth = coerceCssPixelValue(this._config.minWidth);
|
|
style.minHeight = coerceCssPixelValue(this._config.minHeight);
|
|
style.maxWidth = coerceCssPixelValue(this._config.maxWidth);
|
|
style.maxHeight = coerceCssPixelValue(this._config.maxHeight);
|
|
}
|
|
/** Toggles the pointer events for the overlay pane element. */
|
|
_togglePointerEvents(enablePointer) {
|
|
this._pane.style.pointerEvents = enablePointer ? "" : "none";
|
|
}
|
|
/** Attaches a backdrop for this overlay. */
|
|
_attachBackdrop() {
|
|
const showingClass = "cdk-overlay-backdrop-showing";
|
|
this._backdropElement = this._document.createElement("div");
|
|
this._backdropElement.classList.add("cdk-overlay-backdrop");
|
|
if (this._animationsDisabled) {
|
|
this._backdropElement.classList.add("cdk-overlay-backdrop-noop-animation");
|
|
}
|
|
if (this._config.backdropClass) {
|
|
this._toggleClasses(this._backdropElement, this._config.backdropClass, true);
|
|
}
|
|
this._host.parentElement.insertBefore(this._backdropElement, this._host);
|
|
this._backdropElement.addEventListener("click", this._backdropClickHandler);
|
|
if (!this._animationsDisabled && typeof requestAnimationFrame !== "undefined") {
|
|
this._ngZone.runOutsideAngular(() => {
|
|
requestAnimationFrame(() => {
|
|
if (this._backdropElement) {
|
|
this._backdropElement.classList.add(showingClass);
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
this._backdropElement.classList.add(showingClass);
|
|
}
|
|
}
|
|
/**
|
|
* Updates the stacking order of the element, moving it to the top if necessary.
|
|
* This is required in cases where one overlay was detached, while another one,
|
|
* that should be behind it, was destroyed. The next time both of them are opened,
|
|
* the stacking will be wrong, because the detached element's pane will still be
|
|
* in its original DOM position.
|
|
*/
|
|
_updateStackingOrder() {
|
|
if (this._host.nextSibling) {
|
|
this._host.parentNode.appendChild(this._host);
|
|
}
|
|
}
|
|
/** Detaches the backdrop (if any) associated with the overlay. */
|
|
detachBackdrop() {
|
|
const backdropToDetach = this._backdropElement;
|
|
if (!backdropToDetach) {
|
|
return;
|
|
}
|
|
if (this._animationsDisabled) {
|
|
this._disposeBackdrop(backdropToDetach);
|
|
return;
|
|
}
|
|
backdropToDetach.classList.remove("cdk-overlay-backdrop-showing");
|
|
this._ngZone.runOutsideAngular(() => {
|
|
backdropToDetach.addEventListener("transitionend", this._backdropTransitionendHandler);
|
|
});
|
|
backdropToDetach.style.pointerEvents = "none";
|
|
this._backdropTimeout = this._ngZone.runOutsideAngular(() => setTimeout(() => {
|
|
this._disposeBackdrop(backdropToDetach);
|
|
}, 500));
|
|
}
|
|
/** Toggles a single CSS class or an array of classes on an element. */
|
|
_toggleClasses(element, cssClasses, isAdd) {
|
|
const classes = coerceArray(cssClasses || []).filter((c) => !!c);
|
|
if (classes.length) {
|
|
isAdd ? element.classList.add(...classes) : element.classList.remove(...classes);
|
|
}
|
|
}
|
|
/** Detaches the overlay content next time the zone stabilizes. */
|
|
_detachContentWhenStable() {
|
|
this._ngZone.runOutsideAngular(() => {
|
|
const subscription = this._ngZone.onStable.pipe(takeUntil(merge(this._attachments, this._detachments))).subscribe(() => {
|
|
if (!this._pane || !this._host || this._pane.children.length === 0) {
|
|
if (this._pane && this._config.panelClass) {
|
|
this._toggleClasses(this._pane, this._config.panelClass, false);
|
|
}
|
|
if (this._host && this._host.parentElement) {
|
|
this._previousHostParent = this._host.parentElement;
|
|
this._host.remove();
|
|
}
|
|
subscription.unsubscribe();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
/** Disposes of a scroll strategy. */
|
|
_disposeScrollStrategy() {
|
|
const scrollStrategy = this._scrollStrategy;
|
|
if (scrollStrategy) {
|
|
scrollStrategy.disable();
|
|
if (scrollStrategy.detach) {
|
|
scrollStrategy.detach();
|
|
}
|
|
}
|
|
}
|
|
/** Removes a backdrop element from the DOM. */
|
|
_disposeBackdrop(backdrop) {
|
|
if (backdrop) {
|
|
backdrop.removeEventListener("click", this._backdropClickHandler);
|
|
backdrop.removeEventListener("transitionend", this._backdropTransitionendHandler);
|
|
backdrop.remove();
|
|
if (this._backdropElement === backdrop) {
|
|
this._backdropElement = null;
|
|
}
|
|
}
|
|
if (this._backdropTimeout) {
|
|
clearTimeout(this._backdropTimeout);
|
|
this._backdropTimeout = void 0;
|
|
}
|
|
}
|
|
};
|
|
var boundingBoxClass = "cdk-overlay-connected-position-bounding-box";
|
|
var cssUnitPattern = /([A-Za-z%]+)$/;
|
|
var FlexibleConnectedPositionStrategy = class {
|
|
/** Ordered list of preferred positions, from most to least desirable. */
|
|
get positions() {
|
|
return this._preferredPositions;
|
|
}
|
|
constructor(connectedTo, _viewportRuler, _document, _platform, _overlayContainer) {
|
|
this._viewportRuler = _viewportRuler;
|
|
this._document = _document;
|
|
this._platform = _platform;
|
|
this._overlayContainer = _overlayContainer;
|
|
this._lastBoundingBoxSize = {
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
this._isPushed = false;
|
|
this._canPush = true;
|
|
this._growAfterOpen = false;
|
|
this._hasFlexibleDimensions = true;
|
|
this._positionLocked = false;
|
|
this._viewportMargin = 0;
|
|
this._scrollables = [];
|
|
this._preferredPositions = [];
|
|
this._positionChanges = new Subject();
|
|
this._resizeSubscription = Subscription.EMPTY;
|
|
this._offsetX = 0;
|
|
this._offsetY = 0;
|
|
this._appliedPanelClasses = [];
|
|
this.positionChanges = this._positionChanges;
|
|
this.setOrigin(connectedTo);
|
|
}
|
|
/** Attaches this position strategy to an overlay. */
|
|
attach(overlayRef) {
|
|
if (this._overlayRef && overlayRef !== this._overlayRef && (typeof ngDevMode === "undefined" || ngDevMode)) {
|
|
throw Error("This position strategy is already attached to an overlay");
|
|
}
|
|
this._validatePositions();
|
|
overlayRef.hostElement.classList.add(boundingBoxClass);
|
|
this._overlayRef = overlayRef;
|
|
this._boundingBox = overlayRef.hostElement;
|
|
this._pane = overlayRef.overlayElement;
|
|
this._isDisposed = false;
|
|
this._isInitialRender = true;
|
|
this._lastPosition = null;
|
|
this._resizeSubscription.unsubscribe();
|
|
this._resizeSubscription = this._viewportRuler.change().subscribe(() => {
|
|
this._isInitialRender = true;
|
|
this.apply();
|
|
});
|
|
}
|
|
/**
|
|
* Updates the position of the overlay element, using whichever preferred position relative
|
|
* to the origin best fits on-screen.
|
|
*
|
|
* The selection of a position goes as follows:
|
|
* - If any positions fit completely within the viewport as-is,
|
|
* choose the first position that does so.
|
|
* - If flexible dimensions are enabled and at least one satisfies the given minimum width/height,
|
|
* choose the position with the greatest available size modified by the positions' weight.
|
|
* - If pushing is enabled, take the position that went off-screen the least and push it
|
|
* on-screen.
|
|
* - If none of the previous criteria were met, use the position that goes off-screen the least.
|
|
* @docs-private
|
|
*/
|
|
apply() {
|
|
if (this._isDisposed || !this._platform.isBrowser) {
|
|
return;
|
|
}
|
|
if (!this._isInitialRender && this._positionLocked && this._lastPosition) {
|
|
this.reapplyLastPosition();
|
|
return;
|
|
}
|
|
this._clearPanelClasses();
|
|
this._resetOverlayElementStyles();
|
|
this._resetBoundingBoxStyles();
|
|
this._viewportRect = this._getNarrowedViewportRect();
|
|
this._originRect = this._getOriginRect();
|
|
this._overlayRect = this._pane.getBoundingClientRect();
|
|
this._containerRect = this._overlayContainer.getContainerElement().getBoundingClientRect();
|
|
const originRect = this._originRect;
|
|
const overlayRect = this._overlayRect;
|
|
const viewportRect = this._viewportRect;
|
|
const containerRect = this._containerRect;
|
|
const flexibleFits = [];
|
|
let fallback;
|
|
for (let pos of this._preferredPositions) {
|
|
let originPoint = this._getOriginPoint(originRect, containerRect, pos);
|
|
let overlayPoint = this._getOverlayPoint(originPoint, overlayRect, pos);
|
|
let overlayFit = this._getOverlayFit(overlayPoint, overlayRect, viewportRect, pos);
|
|
if (overlayFit.isCompletelyWithinViewport) {
|
|
this._isPushed = false;
|
|
this._applyPosition(pos, originPoint);
|
|
return;
|
|
}
|
|
if (this._canFitWithFlexibleDimensions(overlayFit, overlayPoint, viewportRect)) {
|
|
flexibleFits.push({
|
|
position: pos,
|
|
origin: originPoint,
|
|
overlayRect,
|
|
boundingBoxRect: this._calculateBoundingBoxRect(originPoint, pos)
|
|
});
|
|
continue;
|
|
}
|
|
if (!fallback || fallback.overlayFit.visibleArea < overlayFit.visibleArea) {
|
|
fallback = {
|
|
overlayFit,
|
|
overlayPoint,
|
|
originPoint,
|
|
position: pos,
|
|
overlayRect
|
|
};
|
|
}
|
|
}
|
|
if (flexibleFits.length) {
|
|
let bestFit = null;
|
|
let bestScore = -1;
|
|
for (const fit of flexibleFits) {
|
|
const score = fit.boundingBoxRect.width * fit.boundingBoxRect.height * (fit.position.weight || 1);
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
bestFit = fit;
|
|
}
|
|
}
|
|
this._isPushed = false;
|
|
this._applyPosition(bestFit.position, bestFit.origin);
|
|
return;
|
|
}
|
|
if (this._canPush) {
|
|
this._isPushed = true;
|
|
this._applyPosition(fallback.position, fallback.originPoint);
|
|
return;
|
|
}
|
|
this._applyPosition(fallback.position, fallback.originPoint);
|
|
}
|
|
detach() {
|
|
this._clearPanelClasses();
|
|
this._lastPosition = null;
|
|
this._previousPushAmount = null;
|
|
this._resizeSubscription.unsubscribe();
|
|
}
|
|
/** Cleanup after the element gets destroyed. */
|
|
dispose() {
|
|
if (this._isDisposed) {
|
|
return;
|
|
}
|
|
if (this._boundingBox) {
|
|
extendStyles(this._boundingBox.style, {
|
|
top: "",
|
|
left: "",
|
|
right: "",
|
|
bottom: "",
|
|
height: "",
|
|
width: "",
|
|
alignItems: "",
|
|
justifyContent: ""
|
|
});
|
|
}
|
|
if (this._pane) {
|
|
this._resetOverlayElementStyles();
|
|
}
|
|
if (this._overlayRef) {
|
|
this._overlayRef.hostElement.classList.remove(boundingBoxClass);
|
|
}
|
|
this.detach();
|
|
this._positionChanges.complete();
|
|
this._overlayRef = this._boundingBox = null;
|
|
this._isDisposed = true;
|
|
}
|
|
/**
|
|
* This re-aligns the overlay element with the trigger in its last calculated position,
|
|
* even if a position higher in the "preferred positions" list would now fit. This
|
|
* allows one to re-align the panel without changing the orientation of the panel.
|
|
*/
|
|
reapplyLastPosition() {
|
|
if (this._isDisposed || !this._platform.isBrowser) {
|
|
return;
|
|
}
|
|
const lastPosition = this._lastPosition;
|
|
if (lastPosition) {
|
|
this._originRect = this._getOriginRect();
|
|
this._overlayRect = this._pane.getBoundingClientRect();
|
|
this._viewportRect = this._getNarrowedViewportRect();
|
|
this._containerRect = this._overlayContainer.getContainerElement().getBoundingClientRect();
|
|
const originPoint = this._getOriginPoint(this._originRect, this._containerRect, lastPosition);
|
|
this._applyPosition(lastPosition, originPoint);
|
|
} else {
|
|
this.apply();
|
|
}
|
|
}
|
|
/**
|
|
* Sets the list of Scrollable containers that host the origin element so that
|
|
* on reposition we can evaluate if it or the overlay has been clipped or outside view. Every
|
|
* Scrollable must be an ancestor element of the strategy's origin element.
|
|
*/
|
|
withScrollableContainers(scrollables) {
|
|
this._scrollables = scrollables;
|
|
return this;
|
|
}
|
|
/**
|
|
* Adds new preferred positions.
|
|
* @param positions List of positions options for this overlay.
|
|
*/
|
|
withPositions(positions) {
|
|
this._preferredPositions = positions;
|
|
if (positions.indexOf(this._lastPosition) === -1) {
|
|
this._lastPosition = null;
|
|
}
|
|
this._validatePositions();
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets a minimum distance the overlay may be positioned to the edge of the viewport.
|
|
* @param margin Required margin between the overlay and the viewport edge in pixels.
|
|
*/
|
|
withViewportMargin(margin) {
|
|
this._viewportMargin = margin;
|
|
return this;
|
|
}
|
|
/** Sets whether the overlay's width and height can be constrained to fit within the viewport. */
|
|
withFlexibleDimensions(flexibleDimensions = true) {
|
|
this._hasFlexibleDimensions = flexibleDimensions;
|
|
return this;
|
|
}
|
|
/** Sets whether the overlay can grow after the initial open via flexible width/height. */
|
|
withGrowAfterOpen(growAfterOpen = true) {
|
|
this._growAfterOpen = growAfterOpen;
|
|
return this;
|
|
}
|
|
/** Sets whether the overlay can be pushed on-screen if none of the provided positions fit. */
|
|
withPush(canPush = true) {
|
|
this._canPush = canPush;
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets whether the overlay's position should be locked in after it is positioned
|
|
* initially. When an overlay is locked in, it won't attempt to reposition itself
|
|
* when the position is re-applied (e.g. when the user scrolls away).
|
|
* @param isLocked Whether the overlay should locked in.
|
|
*/
|
|
withLockedPosition(isLocked = true) {
|
|
this._positionLocked = isLocked;
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the origin, relative to which to position the overlay.
|
|
* Using an element origin is useful for building components that need to be positioned
|
|
* relatively to a trigger (e.g. dropdown menus or tooltips), whereas using a point can be
|
|
* used for cases like contextual menus which open relative to the user's pointer.
|
|
* @param origin Reference to the new origin.
|
|
*/
|
|
setOrigin(origin) {
|
|
this._origin = origin;
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the default offset for the overlay's connection point on the x-axis.
|
|
* @param offset New offset in the X axis.
|
|
*/
|
|
withDefaultOffsetX(offset) {
|
|
this._offsetX = offset;
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the default offset for the overlay's connection point on the y-axis.
|
|
* @param offset New offset in the Y axis.
|
|
*/
|
|
withDefaultOffsetY(offset) {
|
|
this._offsetY = offset;
|
|
return this;
|
|
}
|
|
/**
|
|
* Configures that the position strategy should set a `transform-origin` on some elements
|
|
* inside the overlay, depending on the current position that is being applied. This is
|
|
* useful for the cases where the origin of an animation can change depending on the
|
|
* alignment of the overlay.
|
|
* @param selector CSS selector that will be used to find the target
|
|
* elements onto which to set the transform origin.
|
|
*/
|
|
withTransformOriginOn(selector) {
|
|
this._transformOriginSelector = selector;
|
|
return this;
|
|
}
|
|
/**
|
|
* Gets the (x, y) coordinate of a connection point on the origin based on a relative position.
|
|
*/
|
|
_getOriginPoint(originRect, containerRect, pos) {
|
|
let x;
|
|
if (pos.originX == "center") {
|
|
x = originRect.left + originRect.width / 2;
|
|
} else {
|
|
const startX = this._isRtl() ? originRect.right : originRect.left;
|
|
const endX = this._isRtl() ? originRect.left : originRect.right;
|
|
x = pos.originX == "start" ? startX : endX;
|
|
}
|
|
if (containerRect.left < 0) {
|
|
x -= containerRect.left;
|
|
}
|
|
let y;
|
|
if (pos.originY == "center") {
|
|
y = originRect.top + originRect.height / 2;
|
|
} else {
|
|
y = pos.originY == "top" ? originRect.top : originRect.bottom;
|
|
}
|
|
if (containerRect.top < 0) {
|
|
y -= containerRect.top;
|
|
}
|
|
return {
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
/**
|
|
* Gets the (x, y) coordinate of the top-left corner of the overlay given a given position and
|
|
* origin point to which the overlay should be connected.
|
|
*/
|
|
_getOverlayPoint(originPoint, overlayRect, pos) {
|
|
let overlayStartX;
|
|
if (pos.overlayX == "center") {
|
|
overlayStartX = -overlayRect.width / 2;
|
|
} else if (pos.overlayX === "start") {
|
|
overlayStartX = this._isRtl() ? -overlayRect.width : 0;
|
|
} else {
|
|
overlayStartX = this._isRtl() ? 0 : -overlayRect.width;
|
|
}
|
|
let overlayStartY;
|
|
if (pos.overlayY == "center") {
|
|
overlayStartY = -overlayRect.height / 2;
|
|
} else {
|
|
overlayStartY = pos.overlayY == "top" ? 0 : -overlayRect.height;
|
|
}
|
|
return {
|
|
x: originPoint.x + overlayStartX,
|
|
y: originPoint.y + overlayStartY
|
|
};
|
|
}
|
|
/** Gets how well an overlay at the given point will fit within the viewport. */
|
|
_getOverlayFit(point, rawOverlayRect, viewport, position) {
|
|
const overlay = getRoundedBoundingClientRect(rawOverlayRect);
|
|
let {
|
|
x,
|
|
y
|
|
} = point;
|
|
let offsetX = this._getOffset(position, "x");
|
|
let offsetY = this._getOffset(position, "y");
|
|
if (offsetX) {
|
|
x += offsetX;
|
|
}
|
|
if (offsetY) {
|
|
y += offsetY;
|
|
}
|
|
let leftOverflow = 0 - x;
|
|
let rightOverflow = x + overlay.width - viewport.width;
|
|
let topOverflow = 0 - y;
|
|
let bottomOverflow = y + overlay.height - viewport.height;
|
|
let visibleWidth = this._subtractOverflows(overlay.width, leftOverflow, rightOverflow);
|
|
let visibleHeight = this._subtractOverflows(overlay.height, topOverflow, bottomOverflow);
|
|
let visibleArea = visibleWidth * visibleHeight;
|
|
return {
|
|
visibleArea,
|
|
isCompletelyWithinViewport: overlay.width * overlay.height === visibleArea,
|
|
fitsInViewportVertically: visibleHeight === overlay.height,
|
|
fitsInViewportHorizontally: visibleWidth == overlay.width
|
|
};
|
|
}
|
|
/**
|
|
* Whether the overlay can fit within the viewport when it may resize either its width or height.
|
|
* @param fit How well the overlay fits in the viewport at some position.
|
|
* @param point The (x, y) coordinates of the overlay at some position.
|
|
* @param viewport The geometry of the viewport.
|
|
*/
|
|
_canFitWithFlexibleDimensions(fit, point, viewport) {
|
|
if (this._hasFlexibleDimensions) {
|
|
const availableHeight = viewport.bottom - point.y;
|
|
const availableWidth = viewport.right - point.x;
|
|
const minHeight = getPixelValue(this._overlayRef.getConfig().minHeight);
|
|
const minWidth = getPixelValue(this._overlayRef.getConfig().minWidth);
|
|
const verticalFit = fit.fitsInViewportVertically || minHeight != null && minHeight <= availableHeight;
|
|
const horizontalFit = fit.fitsInViewportHorizontally || minWidth != null && minWidth <= availableWidth;
|
|
return verticalFit && horizontalFit;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Gets the point at which the overlay can be "pushed" on-screen. If the overlay is larger than
|
|
* the viewport, the top-left corner will be pushed on-screen (with overflow occurring on the
|
|
* right and bottom).
|
|
*
|
|
* @param start Starting point from which the overlay is pushed.
|
|
* @param rawOverlayRect Dimensions of the overlay.
|
|
* @param scrollPosition Current viewport scroll position.
|
|
* @returns The point at which to position the overlay after pushing. This is effectively a new
|
|
* originPoint.
|
|
*/
|
|
_pushOverlayOnScreen(start, rawOverlayRect, scrollPosition) {
|
|
if (this._previousPushAmount && this._positionLocked) {
|
|
return {
|
|
x: start.x + this._previousPushAmount.x,
|
|
y: start.y + this._previousPushAmount.y
|
|
};
|
|
}
|
|
const overlay = getRoundedBoundingClientRect(rawOverlayRect);
|
|
const viewport = this._viewportRect;
|
|
const overflowRight = Math.max(start.x + overlay.width - viewport.width, 0);
|
|
const overflowBottom = Math.max(start.y + overlay.height - viewport.height, 0);
|
|
const overflowTop = Math.max(viewport.top - scrollPosition.top - start.y, 0);
|
|
const overflowLeft = Math.max(viewport.left - scrollPosition.left - start.x, 0);
|
|
let pushX = 0;
|
|
let pushY = 0;
|
|
if (overlay.width <= viewport.width) {
|
|
pushX = overflowLeft || -overflowRight;
|
|
} else {
|
|
pushX = start.x < this._viewportMargin ? viewport.left - scrollPosition.left - start.x : 0;
|
|
}
|
|
if (overlay.height <= viewport.height) {
|
|
pushY = overflowTop || -overflowBottom;
|
|
} else {
|
|
pushY = start.y < this._viewportMargin ? viewport.top - scrollPosition.top - start.y : 0;
|
|
}
|
|
this._previousPushAmount = {
|
|
x: pushX,
|
|
y: pushY
|
|
};
|
|
return {
|
|
x: start.x + pushX,
|
|
y: start.y + pushY
|
|
};
|
|
}
|
|
/**
|
|
* Applies a computed position to the overlay and emits a position change.
|
|
* @param position The position preference
|
|
* @param originPoint The point on the origin element where the overlay is connected.
|
|
*/
|
|
_applyPosition(position, originPoint) {
|
|
this._setTransformOrigin(position);
|
|
this._setOverlayElementStyles(originPoint, position);
|
|
this._setBoundingBoxStyles(originPoint, position);
|
|
if (position.panelClass) {
|
|
this._addPanelClasses(position.panelClass);
|
|
}
|
|
this._lastPosition = position;
|
|
if (this._positionChanges.observers.length) {
|
|
const scrollableViewProperties = this._getScrollVisibility();
|
|
const changeEvent = new ConnectedOverlayPositionChange(position, scrollableViewProperties);
|
|
this._positionChanges.next(changeEvent);
|
|
}
|
|
this._isInitialRender = false;
|
|
}
|
|
/** Sets the transform origin based on the configured selector and the passed-in position. */
|
|
_setTransformOrigin(position) {
|
|
if (!this._transformOriginSelector) {
|
|
return;
|
|
}
|
|
const elements = this._boundingBox.querySelectorAll(this._transformOriginSelector);
|
|
let xOrigin;
|
|
let yOrigin = position.overlayY;
|
|
if (position.overlayX === "center") {
|
|
xOrigin = "center";
|
|
} else if (this._isRtl()) {
|
|
xOrigin = position.overlayX === "start" ? "right" : "left";
|
|
} else {
|
|
xOrigin = position.overlayX === "start" ? "left" : "right";
|
|
}
|
|
for (let i = 0; i < elements.length; i++) {
|
|
elements[i].style.transformOrigin = `${xOrigin} ${yOrigin}`;
|
|
}
|
|
}
|
|
/**
|
|
* Gets the position and size of the overlay's sizing container.
|
|
*
|
|
* This method does no measuring and applies no styles so that we can cheaply compute the
|
|
* bounds for all positions and choose the best fit based on these results.
|
|
*/
|
|
_calculateBoundingBoxRect(origin, position) {
|
|
const viewport = this._viewportRect;
|
|
const isRtl = this._isRtl();
|
|
let height, top, bottom;
|
|
if (position.overlayY === "top") {
|
|
top = origin.y;
|
|
height = viewport.height - top + this._viewportMargin;
|
|
} else if (position.overlayY === "bottom") {
|
|
bottom = viewport.height - origin.y + this._viewportMargin * 2;
|
|
height = viewport.height - bottom + this._viewportMargin;
|
|
} else {
|
|
const smallestDistanceToViewportEdge = Math.min(viewport.bottom - origin.y + viewport.top, origin.y);
|
|
const previousHeight = this._lastBoundingBoxSize.height;
|
|
height = smallestDistanceToViewportEdge * 2;
|
|
top = origin.y - smallestDistanceToViewportEdge;
|
|
if (height > previousHeight && !this._isInitialRender && !this._growAfterOpen) {
|
|
top = origin.y - previousHeight / 2;
|
|
}
|
|
}
|
|
const isBoundedByRightViewportEdge = position.overlayX === "start" && !isRtl || position.overlayX === "end" && isRtl;
|
|
const isBoundedByLeftViewportEdge = position.overlayX === "end" && !isRtl || position.overlayX === "start" && isRtl;
|
|
let width, left, right;
|
|
if (isBoundedByLeftViewportEdge) {
|
|
right = viewport.width - origin.x + this._viewportMargin;
|
|
width = origin.x - this._viewportMargin;
|
|
} else if (isBoundedByRightViewportEdge) {
|
|
left = origin.x;
|
|
width = viewport.right - origin.x;
|
|
} else {
|
|
const smallestDistanceToViewportEdge = Math.min(viewport.right - origin.x + viewport.left, origin.x);
|
|
const previousWidth = this._lastBoundingBoxSize.width;
|
|
width = smallestDistanceToViewportEdge * 2;
|
|
left = origin.x - smallestDistanceToViewportEdge;
|
|
if (width > previousWidth && !this._isInitialRender && !this._growAfterOpen) {
|
|
left = origin.x - previousWidth / 2;
|
|
}
|
|
}
|
|
return {
|
|
top,
|
|
left,
|
|
bottom,
|
|
right,
|
|
width,
|
|
height
|
|
};
|
|
}
|
|
/**
|
|
* Sets the position and size of the overlay's sizing wrapper. The wrapper is positioned on the
|
|
* origin's connection point and stretches to the bounds of the viewport.
|
|
*
|
|
* @param origin The point on the origin element where the overlay is connected.
|
|
* @param position The position preference
|
|
*/
|
|
_setBoundingBoxStyles(origin, position) {
|
|
const boundingBoxRect = this._calculateBoundingBoxRect(origin, position);
|
|
if (!this._isInitialRender && !this._growAfterOpen) {
|
|
boundingBoxRect.height = Math.min(boundingBoxRect.height, this._lastBoundingBoxSize.height);
|
|
boundingBoxRect.width = Math.min(boundingBoxRect.width, this._lastBoundingBoxSize.width);
|
|
}
|
|
const styles = {};
|
|
if (this._hasExactPosition()) {
|
|
styles.top = styles.left = "0";
|
|
styles.bottom = styles.right = styles.maxHeight = styles.maxWidth = "";
|
|
styles.width = styles.height = "100%";
|
|
} else {
|
|
const maxHeight = this._overlayRef.getConfig().maxHeight;
|
|
const maxWidth = this._overlayRef.getConfig().maxWidth;
|
|
styles.height = coerceCssPixelValue(boundingBoxRect.height);
|
|
styles.top = coerceCssPixelValue(boundingBoxRect.top);
|
|
styles.bottom = coerceCssPixelValue(boundingBoxRect.bottom);
|
|
styles.width = coerceCssPixelValue(boundingBoxRect.width);
|
|
styles.left = coerceCssPixelValue(boundingBoxRect.left);
|
|
styles.right = coerceCssPixelValue(boundingBoxRect.right);
|
|
if (position.overlayX === "center") {
|
|
styles.alignItems = "center";
|
|
} else {
|
|
styles.alignItems = position.overlayX === "end" ? "flex-end" : "flex-start";
|
|
}
|
|
if (position.overlayY === "center") {
|
|
styles.justifyContent = "center";
|
|
} else {
|
|
styles.justifyContent = position.overlayY === "bottom" ? "flex-end" : "flex-start";
|
|
}
|
|
if (maxHeight) {
|
|
styles.maxHeight = coerceCssPixelValue(maxHeight);
|
|
}
|
|
if (maxWidth) {
|
|
styles.maxWidth = coerceCssPixelValue(maxWidth);
|
|
}
|
|
}
|
|
this._lastBoundingBoxSize = boundingBoxRect;
|
|
extendStyles(this._boundingBox.style, styles);
|
|
}
|
|
/** Resets the styles for the bounding box so that a new positioning can be computed. */
|
|
_resetBoundingBoxStyles() {
|
|
extendStyles(this._boundingBox.style, {
|
|
top: "0",
|
|
left: "0",
|
|
right: "0",
|
|
bottom: "0",
|
|
height: "",
|
|
width: "",
|
|
alignItems: "",
|
|
justifyContent: ""
|
|
});
|
|
}
|
|
/** Resets the styles for the overlay pane so that a new positioning can be computed. */
|
|
_resetOverlayElementStyles() {
|
|
extendStyles(this._pane.style, {
|
|
top: "",
|
|
left: "",
|
|
bottom: "",
|
|
right: "",
|
|
position: "",
|
|
transform: ""
|
|
});
|
|
}
|
|
/** Sets positioning styles to the overlay element. */
|
|
_setOverlayElementStyles(originPoint, position) {
|
|
const styles = {};
|
|
const hasExactPosition = this._hasExactPosition();
|
|
const hasFlexibleDimensions = this._hasFlexibleDimensions;
|
|
const config = this._overlayRef.getConfig();
|
|
if (hasExactPosition) {
|
|
const scrollPosition = this._viewportRuler.getViewportScrollPosition();
|
|
extendStyles(styles, this._getExactOverlayY(position, originPoint, scrollPosition));
|
|
extendStyles(styles, this._getExactOverlayX(position, originPoint, scrollPosition));
|
|
} else {
|
|
styles.position = "static";
|
|
}
|
|
let transformString = "";
|
|
let offsetX = this._getOffset(position, "x");
|
|
let offsetY = this._getOffset(position, "y");
|
|
if (offsetX) {
|
|
transformString += `translateX(${offsetX}px) `;
|
|
}
|
|
if (offsetY) {
|
|
transformString += `translateY(${offsetY}px)`;
|
|
}
|
|
styles.transform = transformString.trim();
|
|
if (config.maxHeight) {
|
|
if (hasExactPosition) {
|
|
styles.maxHeight = coerceCssPixelValue(config.maxHeight);
|
|
} else if (hasFlexibleDimensions) {
|
|
styles.maxHeight = "";
|
|
}
|
|
}
|
|
if (config.maxWidth) {
|
|
if (hasExactPosition) {
|
|
styles.maxWidth = coerceCssPixelValue(config.maxWidth);
|
|
} else if (hasFlexibleDimensions) {
|
|
styles.maxWidth = "";
|
|
}
|
|
}
|
|
extendStyles(this._pane.style, styles);
|
|
}
|
|
/** Gets the exact top/bottom for the overlay when not using flexible sizing or when pushing. */
|
|
_getExactOverlayY(position, originPoint, scrollPosition) {
|
|
let styles = {
|
|
top: "",
|
|
bottom: ""
|
|
};
|
|
let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position);
|
|
if (this._isPushed) {
|
|
overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect, scrollPosition);
|
|
}
|
|
if (position.overlayY === "bottom") {
|
|
const documentHeight = this._document.documentElement.clientHeight;
|
|
styles.bottom = `${documentHeight - (overlayPoint.y + this._overlayRect.height)}px`;
|
|
} else {
|
|
styles.top = coerceCssPixelValue(overlayPoint.y);
|
|
}
|
|
return styles;
|
|
}
|
|
/** Gets the exact left/right for the overlay when not using flexible sizing or when pushing. */
|
|
_getExactOverlayX(position, originPoint, scrollPosition) {
|
|
let styles = {
|
|
left: "",
|
|
right: ""
|
|
};
|
|
let overlayPoint = this._getOverlayPoint(originPoint, this._overlayRect, position);
|
|
if (this._isPushed) {
|
|
overlayPoint = this._pushOverlayOnScreen(overlayPoint, this._overlayRect, scrollPosition);
|
|
}
|
|
let horizontalStyleProperty;
|
|
if (this._isRtl()) {
|
|
horizontalStyleProperty = position.overlayX === "end" ? "left" : "right";
|
|
} else {
|
|
horizontalStyleProperty = position.overlayX === "end" ? "right" : "left";
|
|
}
|
|
if (horizontalStyleProperty === "right") {
|
|
const documentWidth = this._document.documentElement.clientWidth;
|
|
styles.right = `${documentWidth - (overlayPoint.x + this._overlayRect.width)}px`;
|
|
} else {
|
|
styles.left = coerceCssPixelValue(overlayPoint.x);
|
|
}
|
|
return styles;
|
|
}
|
|
/**
|
|
* Gets the view properties of the trigger and overlay, including whether they are clipped
|
|
* or completely outside the view of any of the strategy's scrollables.
|
|
*/
|
|
_getScrollVisibility() {
|
|
const originBounds = this._getOriginRect();
|
|
const overlayBounds = this._pane.getBoundingClientRect();
|
|
const scrollContainerBounds = this._scrollables.map((scrollable) => {
|
|
return scrollable.getElementRef().nativeElement.getBoundingClientRect();
|
|
});
|
|
return {
|
|
isOriginClipped: isElementClippedByScrolling(originBounds, scrollContainerBounds),
|
|
isOriginOutsideView: isElementScrolledOutsideView(originBounds, scrollContainerBounds),
|
|
isOverlayClipped: isElementClippedByScrolling(overlayBounds, scrollContainerBounds),
|
|
isOverlayOutsideView: isElementScrolledOutsideView(overlayBounds, scrollContainerBounds)
|
|
};
|
|
}
|
|
/** Subtracts the amount that an element is overflowing on an axis from its length. */
|
|
_subtractOverflows(length, ...overflows) {
|
|
return overflows.reduce((currentValue, currentOverflow) => {
|
|
return currentValue - Math.max(currentOverflow, 0);
|
|
}, length);
|
|
}
|
|
/** Narrows the given viewport rect by the current _viewportMargin. */
|
|
_getNarrowedViewportRect() {
|
|
const width = this._document.documentElement.clientWidth;
|
|
const height = this._document.documentElement.clientHeight;
|
|
const scrollPosition = this._viewportRuler.getViewportScrollPosition();
|
|
return {
|
|
top: scrollPosition.top + this._viewportMargin,
|
|
left: scrollPosition.left + this._viewportMargin,
|
|
right: scrollPosition.left + width - this._viewportMargin,
|
|
bottom: scrollPosition.top + height - this._viewportMargin,
|
|
width: width - 2 * this._viewportMargin,
|
|
height: height - 2 * this._viewportMargin
|
|
};
|
|
}
|
|
/** Whether the we're dealing with an RTL context */
|
|
_isRtl() {
|
|
return this._overlayRef.getDirection() === "rtl";
|
|
}
|
|
/** Determines whether the overlay uses exact or flexible positioning. */
|
|
_hasExactPosition() {
|
|
return !this._hasFlexibleDimensions || this._isPushed;
|
|
}
|
|
/** Retrieves the offset of a position along the x or y axis. */
|
|
_getOffset(position, axis) {
|
|
if (axis === "x") {
|
|
return position.offsetX == null ? this._offsetX : position.offsetX;
|
|
}
|
|
return position.offsetY == null ? this._offsetY : position.offsetY;
|
|
}
|
|
/** Validates that the current position match the expected values. */
|
|
_validatePositions() {
|
|
if (typeof ngDevMode === "undefined" || ngDevMode) {
|
|
if (!this._preferredPositions.length) {
|
|
throw Error("FlexibleConnectedPositionStrategy: At least one position is required.");
|
|
}
|
|
this._preferredPositions.forEach((pair) => {
|
|
validateHorizontalPosition("originX", pair.originX);
|
|
validateVerticalPosition("originY", pair.originY);
|
|
validateHorizontalPosition("overlayX", pair.overlayX);
|
|
validateVerticalPosition("overlayY", pair.overlayY);
|
|
});
|
|
}
|
|
}
|
|
/** Adds a single CSS class or an array of classes on the overlay panel. */
|
|
_addPanelClasses(cssClasses) {
|
|
if (this._pane) {
|
|
coerceArray(cssClasses).forEach((cssClass) => {
|
|
if (cssClass !== "" && this._appliedPanelClasses.indexOf(cssClass) === -1) {
|
|
this._appliedPanelClasses.push(cssClass);
|
|
this._pane.classList.add(cssClass);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
/** Clears the classes that the position strategy has applied from the overlay panel. */
|
|
_clearPanelClasses() {
|
|
if (this._pane) {
|
|
this._appliedPanelClasses.forEach((cssClass) => {
|
|
this._pane.classList.remove(cssClass);
|
|
});
|
|
this._appliedPanelClasses = [];
|
|
}
|
|
}
|
|
/** Returns the ClientRect of the current origin. */
|
|
_getOriginRect() {
|
|
const origin = this._origin;
|
|
if (origin instanceof ElementRef) {
|
|
return origin.nativeElement.getBoundingClientRect();
|
|
}
|
|
if (origin instanceof Element) {
|
|
return origin.getBoundingClientRect();
|
|
}
|
|
const width = origin.width || 0;
|
|
const height = origin.height || 0;
|
|
return {
|
|
top: origin.y,
|
|
bottom: origin.y + height,
|
|
left: origin.x,
|
|
right: origin.x + width,
|
|
height,
|
|
width
|
|
};
|
|
}
|
|
};
|
|
function extendStyles(destination, source) {
|
|
for (let key in source) {
|
|
if (source.hasOwnProperty(key)) {
|
|
destination[key] = source[key];
|
|
}
|
|
}
|
|
return destination;
|
|
}
|
|
function getPixelValue(input) {
|
|
if (typeof input !== "number" && input != null) {
|
|
const [value, units] = input.split(cssUnitPattern);
|
|
return !units || units === "px" ? parseFloat(value) : null;
|
|
}
|
|
return input || null;
|
|
}
|
|
function getRoundedBoundingClientRect(clientRect) {
|
|
return {
|
|
top: Math.floor(clientRect.top),
|
|
right: Math.floor(clientRect.right),
|
|
bottom: Math.floor(clientRect.bottom),
|
|
left: Math.floor(clientRect.left),
|
|
width: Math.floor(clientRect.width),
|
|
height: Math.floor(clientRect.height)
|
|
};
|
|
}
|
|
var wrapperClass = "cdk-global-overlay-wrapper";
|
|
var GlobalPositionStrategy = class {
|
|
constructor() {
|
|
this._cssPosition = "static";
|
|
this._topOffset = "";
|
|
this._bottomOffset = "";
|
|
this._alignItems = "";
|
|
this._xPosition = "";
|
|
this._xOffset = "";
|
|
this._width = "";
|
|
this._height = "";
|
|
this._isDisposed = false;
|
|
}
|
|
attach(overlayRef) {
|
|
const config = overlayRef.getConfig();
|
|
this._overlayRef = overlayRef;
|
|
if (this._width && !config.width) {
|
|
overlayRef.updateSize({
|
|
width: this._width
|
|
});
|
|
}
|
|
if (this._height && !config.height) {
|
|
overlayRef.updateSize({
|
|
height: this._height
|
|
});
|
|
}
|
|
overlayRef.hostElement.classList.add(wrapperClass);
|
|
this._isDisposed = false;
|
|
}
|
|
/**
|
|
* Sets the top position of the overlay. Clears any previously set vertical position.
|
|
* @param value New top offset.
|
|
*/
|
|
top(value = "") {
|
|
this._bottomOffset = "";
|
|
this._topOffset = value;
|
|
this._alignItems = "flex-start";
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the left position of the overlay. Clears any previously set horizontal position.
|
|
* @param value New left offset.
|
|
*/
|
|
left(value = "") {
|
|
this._xOffset = value;
|
|
this._xPosition = "left";
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the bottom position of the overlay. Clears any previously set vertical position.
|
|
* @param value New bottom offset.
|
|
*/
|
|
bottom(value = "") {
|
|
this._topOffset = "";
|
|
this._bottomOffset = value;
|
|
this._alignItems = "flex-end";
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the right position of the overlay. Clears any previously set horizontal position.
|
|
* @param value New right offset.
|
|
*/
|
|
right(value = "") {
|
|
this._xOffset = value;
|
|
this._xPosition = "right";
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the overlay to the start of the viewport, depending on the overlay direction.
|
|
* This will be to the left in LTR layouts and to the right in RTL.
|
|
* @param offset Offset from the edge of the screen.
|
|
*/
|
|
start(value = "") {
|
|
this._xOffset = value;
|
|
this._xPosition = "start";
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the overlay to the end of the viewport, depending on the overlay direction.
|
|
* This will be to the right in LTR layouts and to the left in RTL.
|
|
* @param offset Offset from the edge of the screen.
|
|
*/
|
|
end(value = "") {
|
|
this._xOffset = value;
|
|
this._xPosition = "end";
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the overlay width and clears any previously set width.
|
|
* @param value New width for the overlay
|
|
* @deprecated Pass the `width` through the `OverlayConfig`.
|
|
* @breaking-change 8.0.0
|
|
*/
|
|
width(value = "") {
|
|
if (this._overlayRef) {
|
|
this._overlayRef.updateSize({
|
|
width: value
|
|
});
|
|
} else {
|
|
this._width = value;
|
|
}
|
|
return this;
|
|
}
|
|
/**
|
|
* Sets the overlay height and clears any previously set height.
|
|
* @param value New height for the overlay
|
|
* @deprecated Pass the `height` through the `OverlayConfig`.
|
|
* @breaking-change 8.0.0
|
|
*/
|
|
height(value = "") {
|
|
if (this._overlayRef) {
|
|
this._overlayRef.updateSize({
|
|
height: value
|
|
});
|
|
} else {
|
|
this._height = value;
|
|
}
|
|
return this;
|
|
}
|
|
/**
|
|
* Centers the overlay horizontally with an optional offset.
|
|
* Clears any previously set horizontal position.
|
|
*
|
|
* @param offset Overlay offset from the horizontal center.
|
|
*/
|
|
centerHorizontally(offset = "") {
|
|
this.left(offset);
|
|
this._xPosition = "center";
|
|
return this;
|
|
}
|
|
/**
|
|
* Centers the overlay vertically with an optional offset.
|
|
* Clears any previously set vertical position.
|
|
*
|
|
* @param offset Overlay offset from the vertical center.
|
|
*/
|
|
centerVertically(offset = "") {
|
|
this.top(offset);
|
|
this._alignItems = "center";
|
|
return this;
|
|
}
|
|
/**
|
|
* Apply the position to the element.
|
|
* @docs-private
|
|
*/
|
|
apply() {
|
|
if (!this._overlayRef || !this._overlayRef.hasAttached()) {
|
|
return;
|
|
}
|
|
const styles = this._overlayRef.overlayElement.style;
|
|
const parentStyles = this._overlayRef.hostElement.style;
|
|
const config = this._overlayRef.getConfig();
|
|
const {
|
|
width,
|
|
height,
|
|
maxWidth,
|
|
maxHeight
|
|
} = config;
|
|
const shouldBeFlushHorizontally = (width === "100%" || width === "100vw") && (!maxWidth || maxWidth === "100%" || maxWidth === "100vw");
|
|
const shouldBeFlushVertically = (height === "100%" || height === "100vh") && (!maxHeight || maxHeight === "100%" || maxHeight === "100vh");
|
|
const xPosition = this._xPosition;
|
|
const xOffset = this._xOffset;
|
|
const isRtl = this._overlayRef.getConfig().direction === "rtl";
|
|
let marginLeft = "";
|
|
let marginRight = "";
|
|
let justifyContent = "";
|
|
if (shouldBeFlushHorizontally) {
|
|
justifyContent = "flex-start";
|
|
} else if (xPosition === "center") {
|
|
justifyContent = "center";
|
|
if (isRtl) {
|
|
marginRight = xOffset;
|
|
} else {
|
|
marginLeft = xOffset;
|
|
}
|
|
} else if (isRtl) {
|
|
if (xPosition === "left" || xPosition === "end") {
|
|
justifyContent = "flex-end";
|
|
marginLeft = xOffset;
|
|
} else if (xPosition === "right" || xPosition === "start") {
|
|
justifyContent = "flex-start";
|
|
marginRight = xOffset;
|
|
}
|
|
} else if (xPosition === "left" || xPosition === "start") {
|
|
justifyContent = "flex-start";
|
|
marginLeft = xOffset;
|
|
} else if (xPosition === "right" || xPosition === "end") {
|
|
justifyContent = "flex-end";
|
|
marginRight = xOffset;
|
|
}
|
|
styles.position = this._cssPosition;
|
|
styles.marginLeft = shouldBeFlushHorizontally ? "0" : marginLeft;
|
|
styles.marginTop = shouldBeFlushVertically ? "0" : this._topOffset;
|
|
styles.marginBottom = this._bottomOffset;
|
|
styles.marginRight = shouldBeFlushHorizontally ? "0" : marginRight;
|
|
parentStyles.justifyContent = justifyContent;
|
|
parentStyles.alignItems = shouldBeFlushVertically ? "flex-start" : this._alignItems;
|
|
}
|
|
/**
|
|
* Cleans up the DOM changes from the position strategy.
|
|
* @docs-private
|
|
*/
|
|
dispose() {
|
|
if (this._isDisposed || !this._overlayRef) {
|
|
return;
|
|
}
|
|
const styles = this._overlayRef.overlayElement.style;
|
|
const parent = this._overlayRef.hostElement;
|
|
const parentStyles = parent.style;
|
|
parent.classList.remove(wrapperClass);
|
|
parentStyles.justifyContent = parentStyles.alignItems = styles.marginTop = styles.marginBottom = styles.marginLeft = styles.marginRight = styles.position = "";
|
|
this._overlayRef = null;
|
|
this._isDisposed = true;
|
|
}
|
|
};
|
|
var _OverlayPositionBuilder = class _OverlayPositionBuilder {
|
|
constructor(_viewportRuler, _document, _platform, _overlayContainer) {
|
|
this._viewportRuler = _viewportRuler;
|
|
this._document = _document;
|
|
this._platform = _platform;
|
|
this._overlayContainer = _overlayContainer;
|
|
}
|
|
/**
|
|
* Creates a global position strategy.
|
|
*/
|
|
global() {
|
|
return new GlobalPositionStrategy();
|
|
}
|
|
/**
|
|
* Creates a flexible position strategy.
|
|
* @param origin Origin relative to which to position the overlay.
|
|
*/
|
|
flexibleConnectedTo(origin) {
|
|
return new FlexibleConnectedPositionStrategy(origin, this._viewportRuler, this._document, this._platform, this._overlayContainer);
|
|
}
|
|
};
|
|
_OverlayPositionBuilder.ɵfac = function OverlayPositionBuilder_Factory(t) {
|
|
return new (t || _OverlayPositionBuilder)(ɵɵinject(ViewportRuler), ɵɵinject(DOCUMENT), ɵɵinject(Platform), ɵɵinject(OverlayContainer));
|
|
};
|
|
_OverlayPositionBuilder.ɵprov = ɵɵdefineInjectable({
|
|
token: _OverlayPositionBuilder,
|
|
factory: _OverlayPositionBuilder.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var OverlayPositionBuilder = _OverlayPositionBuilder;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(OverlayPositionBuilder, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: ViewportRuler
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: Platform
|
|
}, {
|
|
type: OverlayContainer
|
|
}], null);
|
|
})();
|
|
var nextUniqueId = 0;
|
|
var _Overlay = class _Overlay {
|
|
constructor(scrollStrategies, _overlayContainer, _componentFactoryResolver, _positionBuilder, _keyboardDispatcher, _injector, _ngZone, _document, _directionality, _location, _outsideClickDispatcher, _animationsModuleType) {
|
|
this.scrollStrategies = scrollStrategies;
|
|
this._overlayContainer = _overlayContainer;
|
|
this._componentFactoryResolver = _componentFactoryResolver;
|
|
this._positionBuilder = _positionBuilder;
|
|
this._keyboardDispatcher = _keyboardDispatcher;
|
|
this._injector = _injector;
|
|
this._ngZone = _ngZone;
|
|
this._document = _document;
|
|
this._directionality = _directionality;
|
|
this._location = _location;
|
|
this._outsideClickDispatcher = _outsideClickDispatcher;
|
|
this._animationsModuleType = _animationsModuleType;
|
|
}
|
|
/**
|
|
* Creates an overlay.
|
|
* @param config Configuration applied to the overlay.
|
|
* @returns Reference to the created overlay.
|
|
*/
|
|
create(config) {
|
|
const host = this._createHostElement();
|
|
const pane = this._createPaneElement(host);
|
|
const portalOutlet = this._createPortalOutlet(pane);
|
|
const overlayConfig = new OverlayConfig(config);
|
|
overlayConfig.direction = overlayConfig.direction || this._directionality.value;
|
|
return new OverlayRef(portalOutlet, host, pane, overlayConfig, this._ngZone, this._keyboardDispatcher, this._document, this._location, this._outsideClickDispatcher, this._animationsModuleType === "NoopAnimations");
|
|
}
|
|
/**
|
|
* Gets a position builder that can be used, via fluent API,
|
|
* to construct and configure a position strategy.
|
|
* @returns An overlay position builder.
|
|
*/
|
|
position() {
|
|
return this._positionBuilder;
|
|
}
|
|
/**
|
|
* Creates the DOM element for an overlay and appends it to the overlay container.
|
|
* @returns Newly-created pane element
|
|
*/
|
|
_createPaneElement(host) {
|
|
const pane = this._document.createElement("div");
|
|
pane.id = `cdk-overlay-${nextUniqueId++}`;
|
|
pane.classList.add("cdk-overlay-pane");
|
|
host.appendChild(pane);
|
|
return pane;
|
|
}
|
|
/**
|
|
* Creates the host element that wraps around an overlay
|
|
* and can be used for advanced positioning.
|
|
* @returns Newly-create host element.
|
|
*/
|
|
_createHostElement() {
|
|
const host = this._document.createElement("div");
|
|
this._overlayContainer.getContainerElement().appendChild(host);
|
|
return host;
|
|
}
|
|
/**
|
|
* Create a DomPortalOutlet into which the overlay content can be loaded.
|
|
* @param pane The DOM element to turn into a portal outlet.
|
|
* @returns A portal outlet for the given DOM element.
|
|
*/
|
|
_createPortalOutlet(pane) {
|
|
if (!this._appRef) {
|
|
this._appRef = this._injector.get(ApplicationRef);
|
|
}
|
|
return new DomPortalOutlet(pane, this._componentFactoryResolver, this._appRef, this._injector, this._document);
|
|
}
|
|
};
|
|
_Overlay.ɵfac = function Overlay_Factory(t) {
|
|
return new (t || _Overlay)(ɵɵinject(ScrollStrategyOptions), ɵɵinject(OverlayContainer), ɵɵinject(ComponentFactoryResolver$1), ɵɵinject(OverlayPositionBuilder), ɵɵinject(OverlayKeyboardDispatcher), ɵɵinject(Injector), ɵɵinject(NgZone), ɵɵinject(DOCUMENT), ɵɵinject(Directionality), ɵɵinject(Location), ɵɵinject(OverlayOutsideClickDispatcher), ɵɵinject(ANIMATION_MODULE_TYPE, 8));
|
|
};
|
|
_Overlay.ɵprov = ɵɵdefineInjectable({
|
|
token: _Overlay,
|
|
factory: _Overlay.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var Overlay = _Overlay;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(Overlay, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: ScrollStrategyOptions
|
|
}, {
|
|
type: OverlayContainer
|
|
}, {
|
|
type: ComponentFactoryResolver$1
|
|
}, {
|
|
type: OverlayPositionBuilder
|
|
}, {
|
|
type: OverlayKeyboardDispatcher
|
|
}, {
|
|
type: Injector
|
|
}, {
|
|
type: NgZone
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: Directionality
|
|
}, {
|
|
type: Location
|
|
}, {
|
|
type: OverlayOutsideClickDispatcher
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [ANIMATION_MODULE_TYPE]
|
|
}, {
|
|
type: Optional
|
|
}]
|
|
}], null);
|
|
})();
|
|
var defaultPositionList = [{
|
|
originX: "start",
|
|
originY: "bottom",
|
|
overlayX: "start",
|
|
overlayY: "top"
|
|
}, {
|
|
originX: "start",
|
|
originY: "top",
|
|
overlayX: "start",
|
|
overlayY: "bottom"
|
|
}, {
|
|
originX: "end",
|
|
originY: "top",
|
|
overlayX: "end",
|
|
overlayY: "bottom"
|
|
}, {
|
|
originX: "end",
|
|
originY: "bottom",
|
|
overlayX: "end",
|
|
overlayY: "top"
|
|
}];
|
|
var CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY = new InjectionToken("cdk-connected-overlay-scroll-strategy", {
|
|
providedIn: "root",
|
|
factory: () => {
|
|
const overlay = inject(Overlay);
|
|
return () => overlay.scrollStrategies.reposition();
|
|
}
|
|
});
|
|
var _CdkOverlayOrigin = class _CdkOverlayOrigin {
|
|
constructor(elementRef) {
|
|
this.elementRef = elementRef;
|
|
}
|
|
};
|
|
_CdkOverlayOrigin.ɵfac = function CdkOverlayOrigin_Factory(t) {
|
|
return new (t || _CdkOverlayOrigin)(ɵɵdirectiveInject(ElementRef));
|
|
};
|
|
_CdkOverlayOrigin.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkOverlayOrigin,
|
|
selectors: [["", "cdk-overlay-origin", ""], ["", "overlay-origin", ""], ["", "cdkOverlayOrigin", ""]],
|
|
exportAs: ["cdkOverlayOrigin"],
|
|
standalone: true
|
|
});
|
|
var CdkOverlayOrigin = _CdkOverlayOrigin;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkOverlayOrigin, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]",
|
|
exportAs: "cdkOverlayOrigin",
|
|
standalone: true
|
|
}]
|
|
}], () => [{
|
|
type: ElementRef
|
|
}], null);
|
|
})();
|
|
var _CdkConnectedOverlay = class _CdkConnectedOverlay {
|
|
/** The offset in pixels for the overlay connection point on the x-axis */
|
|
get offsetX() {
|
|
return this._offsetX;
|
|
}
|
|
set offsetX(offsetX) {
|
|
this._offsetX = offsetX;
|
|
if (this._position) {
|
|
this._updatePositionStrategy(this._position);
|
|
}
|
|
}
|
|
/** The offset in pixels for the overlay connection point on the y-axis */
|
|
get offsetY() {
|
|
return this._offsetY;
|
|
}
|
|
set offsetY(offsetY) {
|
|
this._offsetY = offsetY;
|
|
if (this._position) {
|
|
this._updatePositionStrategy(this._position);
|
|
}
|
|
}
|
|
/** Whether the overlay should be disposed of when the user goes backwards/forwards in history. */
|
|
get disposeOnNavigation() {
|
|
return this._disposeOnNavigation;
|
|
}
|
|
set disposeOnNavigation(value) {
|
|
this._disposeOnNavigation = value;
|
|
}
|
|
// TODO(jelbourn): inputs for size, scroll behavior, animation, etc.
|
|
constructor(_overlay, templateRef, viewContainerRef, scrollStrategyFactory, _dir) {
|
|
this._overlay = _overlay;
|
|
this._dir = _dir;
|
|
this._backdropSubscription = Subscription.EMPTY;
|
|
this._attachSubscription = Subscription.EMPTY;
|
|
this._detachSubscription = Subscription.EMPTY;
|
|
this._positionSubscription = Subscription.EMPTY;
|
|
this._disposeOnNavigation = false;
|
|
this.viewportMargin = 0;
|
|
this.open = false;
|
|
this.disableClose = false;
|
|
this.hasBackdrop = false;
|
|
this.lockPosition = false;
|
|
this.flexibleDimensions = false;
|
|
this.growAfterOpen = false;
|
|
this.push = false;
|
|
this.backdropClick = new EventEmitter();
|
|
this.positionChange = new EventEmitter();
|
|
this.attach = new EventEmitter();
|
|
this.detach = new EventEmitter();
|
|
this.overlayKeydown = new EventEmitter();
|
|
this.overlayOutsideClick = new EventEmitter();
|
|
this._templatePortal = new TemplatePortal(templateRef, viewContainerRef);
|
|
this._scrollStrategyFactory = scrollStrategyFactory;
|
|
this.scrollStrategy = this._scrollStrategyFactory();
|
|
}
|
|
/** The associated overlay reference. */
|
|
get overlayRef() {
|
|
return this._overlayRef;
|
|
}
|
|
/** The element's layout direction. */
|
|
get dir() {
|
|
return this._dir ? this._dir.value : "ltr";
|
|
}
|
|
ngOnDestroy() {
|
|
this._attachSubscription.unsubscribe();
|
|
this._detachSubscription.unsubscribe();
|
|
this._backdropSubscription.unsubscribe();
|
|
this._positionSubscription.unsubscribe();
|
|
if (this._overlayRef) {
|
|
this._overlayRef.dispose();
|
|
}
|
|
}
|
|
ngOnChanges(changes) {
|
|
if (this._position) {
|
|
this._updatePositionStrategy(this._position);
|
|
this._overlayRef.updateSize({
|
|
width: this.width,
|
|
minWidth: this.minWidth,
|
|
height: this.height,
|
|
minHeight: this.minHeight
|
|
});
|
|
if (changes["origin"] && this.open) {
|
|
this._position.apply();
|
|
}
|
|
}
|
|
if (changes["open"]) {
|
|
this.open ? this._attachOverlay() : this._detachOverlay();
|
|
}
|
|
}
|
|
/** Creates an overlay */
|
|
_createOverlay() {
|
|
if (!this.positions || !this.positions.length) {
|
|
this.positions = defaultPositionList;
|
|
}
|
|
const overlayRef = this._overlayRef = this._overlay.create(this._buildConfig());
|
|
this._attachSubscription = overlayRef.attachments().subscribe(() => this.attach.emit());
|
|
this._detachSubscription = overlayRef.detachments().subscribe(() => this.detach.emit());
|
|
overlayRef.keydownEvents().subscribe((event) => {
|
|
this.overlayKeydown.next(event);
|
|
if (event.keyCode === ESCAPE && !this.disableClose && !hasModifierKey(event)) {
|
|
event.preventDefault();
|
|
this._detachOverlay();
|
|
}
|
|
});
|
|
this._overlayRef.outsidePointerEvents().subscribe((event) => {
|
|
this.overlayOutsideClick.next(event);
|
|
});
|
|
}
|
|
/** Builds the overlay config based on the directive's inputs */
|
|
_buildConfig() {
|
|
const positionStrategy = this._position = this.positionStrategy || this._createPositionStrategy();
|
|
const overlayConfig = new OverlayConfig({
|
|
direction: this._dir,
|
|
positionStrategy,
|
|
scrollStrategy: this.scrollStrategy,
|
|
hasBackdrop: this.hasBackdrop,
|
|
disposeOnNavigation: this.disposeOnNavigation
|
|
});
|
|
if (this.width || this.width === 0) {
|
|
overlayConfig.width = this.width;
|
|
}
|
|
if (this.height || this.height === 0) {
|
|
overlayConfig.height = this.height;
|
|
}
|
|
if (this.minWidth || this.minWidth === 0) {
|
|
overlayConfig.minWidth = this.minWidth;
|
|
}
|
|
if (this.minHeight || this.minHeight === 0) {
|
|
overlayConfig.minHeight = this.minHeight;
|
|
}
|
|
if (this.backdropClass) {
|
|
overlayConfig.backdropClass = this.backdropClass;
|
|
}
|
|
if (this.panelClass) {
|
|
overlayConfig.panelClass = this.panelClass;
|
|
}
|
|
return overlayConfig;
|
|
}
|
|
/** Updates the state of a position strategy, based on the values of the directive inputs. */
|
|
_updatePositionStrategy(positionStrategy) {
|
|
const positions = this.positions.map((currentPosition) => ({
|
|
originX: currentPosition.originX,
|
|
originY: currentPosition.originY,
|
|
overlayX: currentPosition.overlayX,
|
|
overlayY: currentPosition.overlayY,
|
|
offsetX: currentPosition.offsetX || this.offsetX,
|
|
offsetY: currentPosition.offsetY || this.offsetY,
|
|
panelClass: currentPosition.panelClass || void 0
|
|
}));
|
|
return positionStrategy.setOrigin(this._getFlexibleConnectedPositionStrategyOrigin()).withPositions(positions).withFlexibleDimensions(this.flexibleDimensions).withPush(this.push).withGrowAfterOpen(this.growAfterOpen).withViewportMargin(this.viewportMargin).withLockedPosition(this.lockPosition).withTransformOriginOn(this.transformOriginSelector);
|
|
}
|
|
/** Returns the position strategy of the overlay to be set on the overlay config */
|
|
_createPositionStrategy() {
|
|
const strategy = this._overlay.position().flexibleConnectedTo(this._getFlexibleConnectedPositionStrategyOrigin());
|
|
this._updatePositionStrategy(strategy);
|
|
return strategy;
|
|
}
|
|
_getFlexibleConnectedPositionStrategyOrigin() {
|
|
if (this.origin instanceof CdkOverlayOrigin) {
|
|
return this.origin.elementRef;
|
|
} else {
|
|
return this.origin;
|
|
}
|
|
}
|
|
/** Attaches the overlay and subscribes to backdrop clicks if backdrop exists */
|
|
_attachOverlay() {
|
|
if (!this._overlayRef) {
|
|
this._createOverlay();
|
|
} else {
|
|
this._overlayRef.getConfig().hasBackdrop = this.hasBackdrop;
|
|
}
|
|
if (!this._overlayRef.hasAttached()) {
|
|
this._overlayRef.attach(this._templatePortal);
|
|
}
|
|
if (this.hasBackdrop) {
|
|
this._backdropSubscription = this._overlayRef.backdropClick().subscribe((event) => {
|
|
this.backdropClick.emit(event);
|
|
});
|
|
} else {
|
|
this._backdropSubscription.unsubscribe();
|
|
}
|
|
this._positionSubscription.unsubscribe();
|
|
if (this.positionChange.observers.length > 0) {
|
|
this._positionSubscription = this._position.positionChanges.pipe(takeWhile(() => this.positionChange.observers.length > 0)).subscribe((position) => {
|
|
this.positionChange.emit(position);
|
|
if (this.positionChange.observers.length === 0) {
|
|
this._positionSubscription.unsubscribe();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
/** Detaches the overlay and unsubscribes to backdrop clicks if backdrop exists */
|
|
_detachOverlay() {
|
|
if (this._overlayRef) {
|
|
this._overlayRef.detach();
|
|
}
|
|
this._backdropSubscription.unsubscribe();
|
|
this._positionSubscription.unsubscribe();
|
|
}
|
|
};
|
|
_CdkConnectedOverlay.ɵfac = function CdkConnectedOverlay_Factory(t) {
|
|
return new (t || _CdkConnectedOverlay)(ɵɵdirectiveInject(Overlay), ɵɵdirectiveInject(TemplateRef), ɵɵdirectiveInject(ViewContainerRef), ɵɵdirectiveInject(CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY), ɵɵdirectiveInject(Directionality, 8));
|
|
};
|
|
_CdkConnectedOverlay.ɵdir = ɵɵdefineDirective({
|
|
type: _CdkConnectedOverlay,
|
|
selectors: [["", "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", booleanAttribute],
|
|
lockPosition: ["cdkConnectedOverlayLockPosition", "lockPosition", booleanAttribute],
|
|
flexibleDimensions: ["cdkConnectedOverlayFlexibleDimensions", "flexibleDimensions", booleanAttribute],
|
|
growAfterOpen: ["cdkConnectedOverlayGrowAfterOpen", "growAfterOpen", booleanAttribute],
|
|
push: ["cdkConnectedOverlayPush", "push", booleanAttribute],
|
|
disposeOnNavigation: ["cdkConnectedOverlayDisposeOnNavigation", "disposeOnNavigation", booleanAttribute]
|
|
},
|
|
outputs: {
|
|
backdropClick: "backdropClick",
|
|
positionChange: "positionChange",
|
|
attach: "attach",
|
|
detach: "detach",
|
|
overlayKeydown: "overlayKeydown",
|
|
overlayOutsideClick: "overlayOutsideClick"
|
|
},
|
|
exportAs: ["cdkConnectedOverlay"],
|
|
standalone: true,
|
|
features: [ɵɵInputTransformsFeature, ɵɵNgOnChangesFeature]
|
|
});
|
|
var CdkConnectedOverlay = _CdkConnectedOverlay;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(CdkConnectedOverlay, [{
|
|
type: Directive,
|
|
args: [{
|
|
selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]",
|
|
exportAs: "cdkConnectedOverlay",
|
|
standalone: true
|
|
}]
|
|
}], () => [{
|
|
type: Overlay
|
|
}, {
|
|
type: TemplateRef
|
|
}, {
|
|
type: ViewContainerRef
|
|
}, {
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY]
|
|
}]
|
|
}, {
|
|
type: Directionality,
|
|
decorators: [{
|
|
type: Optional
|
|
}]
|
|
}], {
|
|
origin: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayOrigin"]
|
|
}],
|
|
positions: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayPositions"]
|
|
}],
|
|
positionStrategy: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayPositionStrategy"]
|
|
}],
|
|
offsetX: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayOffsetX"]
|
|
}],
|
|
offsetY: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayOffsetY"]
|
|
}],
|
|
width: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayWidth"]
|
|
}],
|
|
height: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayHeight"]
|
|
}],
|
|
minWidth: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayMinWidth"]
|
|
}],
|
|
minHeight: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayMinHeight"]
|
|
}],
|
|
backdropClass: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayBackdropClass"]
|
|
}],
|
|
panelClass: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayPanelClass"]
|
|
}],
|
|
viewportMargin: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayViewportMargin"]
|
|
}],
|
|
scrollStrategy: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayScrollStrategy"]
|
|
}],
|
|
open: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayOpen"]
|
|
}],
|
|
disableClose: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayDisableClose"]
|
|
}],
|
|
transformOriginSelector: [{
|
|
type: Input,
|
|
args: ["cdkConnectedOverlayTransformOriginOn"]
|
|
}],
|
|
hasBackdrop: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkConnectedOverlayHasBackdrop",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
lockPosition: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkConnectedOverlayLockPosition",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
flexibleDimensions: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkConnectedOverlayFlexibleDimensions",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
growAfterOpen: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkConnectedOverlayGrowAfterOpen",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
push: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkConnectedOverlayPush",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
disposeOnNavigation: [{
|
|
type: Input,
|
|
args: [{
|
|
alias: "cdkConnectedOverlayDisposeOnNavigation",
|
|
transform: booleanAttribute
|
|
}]
|
|
}],
|
|
backdropClick: [{
|
|
type: Output
|
|
}],
|
|
positionChange: [{
|
|
type: Output
|
|
}],
|
|
attach: [{
|
|
type: Output
|
|
}],
|
|
detach: [{
|
|
type: Output
|
|
}],
|
|
overlayKeydown: [{
|
|
type: Output
|
|
}],
|
|
overlayOutsideClick: [{
|
|
type: Output
|
|
}]
|
|
});
|
|
})();
|
|
function CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay) {
|
|
return () => overlay.scrollStrategies.reposition();
|
|
}
|
|
var CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER = {
|
|
provide: CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY,
|
|
deps: [Overlay],
|
|
useFactory: CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY
|
|
};
|
|
var _OverlayModule = class _OverlayModule {
|
|
};
|
|
_OverlayModule.ɵfac = function OverlayModule_Factory(t) {
|
|
return new (t || _OverlayModule)();
|
|
};
|
|
_OverlayModule.ɵmod = ɵɵdefineNgModule({
|
|
type: _OverlayModule,
|
|
imports: [BidiModule, PortalModule, ScrollingModule, CdkConnectedOverlay, CdkOverlayOrigin],
|
|
exports: [CdkConnectedOverlay, CdkOverlayOrigin, ScrollingModule]
|
|
});
|
|
_OverlayModule.ɵinj = ɵɵdefineInjector({
|
|
providers: [Overlay, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER],
|
|
imports: [BidiModule, PortalModule, ScrollingModule, ScrollingModule]
|
|
});
|
|
var OverlayModule = _OverlayModule;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(OverlayModule, [{
|
|
type: NgModule,
|
|
args: [{
|
|
imports: [BidiModule, PortalModule, ScrollingModule, CdkConnectedOverlay, CdkOverlayOrigin],
|
|
exports: [CdkConnectedOverlay, CdkOverlayOrigin, ScrollingModule],
|
|
providers: [Overlay, CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER]
|
|
}]
|
|
}], null, null);
|
|
})();
|
|
var _FullscreenOverlayContainer = class _FullscreenOverlayContainer extends OverlayContainer {
|
|
constructor(_document, platform) {
|
|
super(_document, platform);
|
|
}
|
|
ngOnDestroy() {
|
|
super.ngOnDestroy();
|
|
if (this._fullScreenEventName && this._fullScreenListener) {
|
|
this._document.removeEventListener(this._fullScreenEventName, this._fullScreenListener);
|
|
}
|
|
}
|
|
_createContainer() {
|
|
super._createContainer();
|
|
this._adjustParentForFullscreenChange();
|
|
this._addFullscreenChangeListener(() => this._adjustParentForFullscreenChange());
|
|
}
|
|
_adjustParentForFullscreenChange() {
|
|
if (!this._containerElement) {
|
|
return;
|
|
}
|
|
const fullscreenElement = this.getFullscreenElement();
|
|
const parent = fullscreenElement || this._document.body;
|
|
parent.appendChild(this._containerElement);
|
|
}
|
|
_addFullscreenChangeListener(fn) {
|
|
const eventName = this._getEventName();
|
|
if (eventName) {
|
|
if (this._fullScreenListener) {
|
|
this._document.removeEventListener(eventName, this._fullScreenListener);
|
|
}
|
|
this._document.addEventListener(eventName, fn);
|
|
this._fullScreenListener = fn;
|
|
}
|
|
}
|
|
_getEventName() {
|
|
if (!this._fullScreenEventName) {
|
|
const _document = this._document;
|
|
if (_document.fullscreenEnabled) {
|
|
this._fullScreenEventName = "fullscreenchange";
|
|
} else if (_document.webkitFullscreenEnabled) {
|
|
this._fullScreenEventName = "webkitfullscreenchange";
|
|
} else if (_document.mozFullScreenEnabled) {
|
|
this._fullScreenEventName = "mozfullscreenchange";
|
|
} else if (_document.msFullscreenEnabled) {
|
|
this._fullScreenEventName = "MSFullscreenChange";
|
|
}
|
|
}
|
|
return this._fullScreenEventName;
|
|
}
|
|
/**
|
|
* When the page is put into fullscreen mode, a specific element is specified.
|
|
* Only that element and its children are visible when in fullscreen mode.
|
|
*/
|
|
getFullscreenElement() {
|
|
const _document = this._document;
|
|
return _document.fullscreenElement || _document.webkitFullscreenElement || _document.mozFullScreenElement || _document.msFullscreenElement || null;
|
|
}
|
|
};
|
|
_FullscreenOverlayContainer.ɵfac = function FullscreenOverlayContainer_Factory(t) {
|
|
return new (t || _FullscreenOverlayContainer)(ɵɵinject(DOCUMENT), ɵɵinject(Platform));
|
|
};
|
|
_FullscreenOverlayContainer.ɵprov = ɵɵdefineInjectable({
|
|
token: _FullscreenOverlayContainer,
|
|
factory: _FullscreenOverlayContainer.ɵfac,
|
|
providedIn: "root"
|
|
});
|
|
var FullscreenOverlayContainer = _FullscreenOverlayContainer;
|
|
(() => {
|
|
(typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(FullscreenOverlayContainer, [{
|
|
type: Injectable,
|
|
args: [{
|
|
providedIn: "root"
|
|
}]
|
|
}], () => [{
|
|
type: void 0,
|
|
decorators: [{
|
|
type: Inject,
|
|
args: [DOCUMENT]
|
|
}]
|
|
}, {
|
|
type: Platform
|
|
}], null);
|
|
})();
|
|
|
|
export {
|
|
ViewportRuler,
|
|
ComponentPortal,
|
|
TemplatePortal,
|
|
CdkPortalOutlet,
|
|
PortalModule,
|
|
OverlayConfig,
|
|
ConnectionPositionPair,
|
|
OverlayKeyboardDispatcher,
|
|
Overlay,
|
|
CdkOverlayOrigin,
|
|
CdkConnectedOverlay,
|
|
OverlayModule
|
|
};
|
|
//# sourceMappingURL=chunk-6U656R5M.js.map
|
|
|